/*-------------------------------------------------------------------------
 *
 * heap.c
 *	  创建和销毁POSTGRES堆关系的代码
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/heap.c
 *
 *
 * INTERFACE ROUTINES
 *		heap_create()			- 创建一个未分类的堆关系
 *		heap_create_with_catalog() - 创建一个带有分类的关系
 *		heap_drop_with_catalog() - 从分类中移除命名关系
 *
 * NOTES
 *	  该代码取自access/heap/create.c，包含
 *	  较旧的heap_create_with_catalog，amcreate和amdestroy。
 *	  那些程序将很快通过功能管理器调用这些程序，
 *	  就像命名不当的“NewXXX”例程所做的那样。 
 *	  “New”例程将很快被彻底淘汰！
 *		-cim 1/13/91
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/multixact.h"
#include "access/relation.h"
#include "access/table.h"
#include "access/tableam.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_subscription_rel.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"


/* 可能由 pg_upgrade_support 函数设置 */
Oid			binary_upgrade_next_heap_pg_class_oid = InvalidOid;
Oid			binary_upgrade_next_heap_pg_class_relfilenode = InvalidOid;
Oid			binary_upgrade_next_toast_pg_class_oid = InvalidOid;
Oid			binary_upgrade_next_toast_pg_class_relfilenode = InvalidOid;

static void fc_AddNewRelationTuple(Relation pg_class_desc,
								Relation fc_new_rel_desc,
								Oid fc_new_rel_oid,
								Oid fc_new_type_oid,
								Oid fc_reloftype,
								Oid fc_relowner,
								char fc_relkind,
								TransactionId fc_relfrozenxid,
								TransactionId fc_relminmxid,
								Datum fc_relacl,
								Datum fc_reloptions);
static ObjectAddress fc_AddNewRelationType(const char *fc_typeName,
										Oid fc_typeNamespace,
										Oid fc_new_rel_oid,
										char fc_new_rel_kind,
										Oid fc_ownerid,
										Oid fc_new_row_type,
										Oid fc_new_array_type);
static void fc_RelationRemoveInheritance(Oid fc_relid);
static Oid	fc_StoreRelCheck(Relation fc_rel, const char *fc_ccname, Node *fc_expr,
						  bool fc_is_validated, bool fc_is_local, int fc_inhcount,
						  bool fc_is_no_inherit, bool fc_is_internal);
static void fc_StoreConstraints(Relation fc_rel, List *fc_cooked_constraints,
							 bool fc_is_internal);
static bool fc_MergeWithExistingConstraint(Relation fc_rel, const char *fc_ccname, Node *fc_expr,
										bool fc_allow_merge, bool fc_is_local,
										bool fc_is_initially_valid,
										bool fc_is_no_inherit);
static void fc_SetRelationNumChecks(Relation fc_rel, int fc_numchecks);
static Node *fc_cookConstraint(ParseState *fc_pstate,
							Node *fc_raw_constraint,
							char *fc_relname);


/* ----------------------------------------------------------------
 *				XXX 丑陋的硬编码问题 XXX
 *
 *		这些都应该移到 lib/catalog
 *		模块中的某个地方，如果不首先消灭它们。
 * ----------------------------------------------------------------
 */


/*
 * 注意：
 *		未来系统是否应该特殊处理这些属性？
 *		优点：在 ATTRIBUTE 关系中消耗的空间大大减少。
 *		缺点：特殊情况会遍布各处。
 */

/*
 * 以下初始化程序不包括尾随可变长度字段，
 * 但这没关系 - 我们根本不会引用结构的固定大小部分之外的内容。
 * 可以默认归零的字段也未提及。
 */

static const FormData_pg_attribute a1 = {
	.attname = {"ctid"},
	.atttypid = TIDOID,
	.attlen = sizeof(ItemPointerData),
	.attnum = SelfItemPointerAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = false,
	.attalign = TYPALIGN_SHORT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

static const FormData_pg_attribute a2 = {
	.attname = {"xmin"},
	.atttypid = XIDOID,
	.attlen = sizeof(TransactionId),
	.attnum = MinTransactionIdAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = true,
	.attalign = TYPALIGN_INT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

static const FormData_pg_attribute a3 = {
	.attname = {"cmin"},
	.atttypid = CIDOID,
	.attlen = sizeof(CommandId),
	.attnum = MinCommandIdAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = true,
	.attalign = TYPALIGN_INT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

static const FormData_pg_attribute a4 = {
	.attname = {"xmax"},
	.atttypid = XIDOID,
	.attlen = sizeof(TransactionId),
	.attnum = MaxTransactionIdAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = true,
	.attalign = TYPALIGN_INT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

static const FormData_pg_attribute a5 = {
	.attname = {"cmax"},
	.atttypid = CIDOID,
	.attlen = sizeof(CommandId),
	.attnum = MaxCommandIdAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = true,
	.attalign = TYPALIGN_INT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

/*
 * 我们决定将这个属性称为 "tableoid" 而不是 "classoid"，因为
 * 未来可能会有多个同一类/类型的表。无论如何，"table" 仍然是
 * SQL 中使用的词。
 */
static const FormData_pg_attribute a6 = {
	.attname = {"tableoid"},
	.atttypid = OIDOID,
	.attlen = sizeof(Oid),
	.attnum = TableOidAttributeNumber,
	.attcacheoff = -1,
	.atttypmod = -1,
	.attbyval = true,
	.attalign = TYPALIGN_INT,
	.attstorage = TYPSTORAGE_PLAIN,
	.attnotnull = true,
	.attislocal = true,
};

static const FormData_pg_attribute *SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6};

/*
 * 该函数返回系统属性的 Form_pg_attribute 指针。
 * 请注意，如果提供的 attno 无效，我们将 elog，这只会在上游存在问题时发生。
 */
const FormData_pg_attribute *
SystemAttributeDefinition(AttrNumber fc_attno)
{
	if (fc_attno >= 0 || fc_attno < -(int) lengthof(SysAtt))
		elog(ERROR, "invalid system attribute number %d", fc_attno);
	return SysAtt[-fc_attno - 1];
}

/*
 * 如果给定名称是系统属性名称，则返回原型定义的
 * Form_pg_attribute 指针。如果不是，则返回 NULL。
 */
const FormData_pg_attribute *
SystemAttributeByName(const char *fc_attname)
{
	int			fc_j;

	for (fc_j = 0; fc_j < (int) lengthof(SysAtt); fc_j++)
	{
		const FormData_pg_attribute *fc_att = SysAtt[fc_j];

		if (strcmp(NameStr(fc_att->attname), fc_attname) == 0)
			return fc_att;
	}

	return NULL;
}


/* ----------------------------------------------------------------
 *				XXX 丑陋的硬编码问题结束 XXX
 * ---------------------------------------------------------------- */


/* ----------------------------------------------------------------
 *		heap_create		- 创建一个未编目堆关系
 *
 *		注意 API 变化：调用者现在必须始终提供用于关系的 OID。
 *		relfilenode 可以（在最简单的情况下是）留空。
 *
 *		create_storage 指示是否创建存储。
 *		然而，即使 create_storage 为真，如果 relkind 是没有
 *		存储的类型，也不会创建存储。
 *
 *		rel->rd_rel 由 RelationBuildLocalRelation 初始化，
 *		返回时大部分为零。
 * ----------------------------------------------------------------
 */
Relation heap_create(const char *fc_relname,
			Oid fc_relnamespace,
			Oid fc_reltablespace,
			Oid fc_relid,
			Oid fc_relfilenode,
			Oid fc_accessmtd,
			TupleDesc fc_tupDesc,
			char fc_relkind,
			char fc_relpersistence,
			bool fc_shared_relation,
			bool fc_mapped_relation,
			bool fc_allow_system_table_mods,
			TransactionId *fc_relfrozenxid,
			MultiXactId *fc_relminmxid,
			bool fc_create_storage)
{
	Relation	fc_rel;

	/* 调用者必须为关系提供 OID。 */
	Assert(OidIsValid(fc_relid));

	/*
	 * 不允许直接在 pg_catalog 中创建关系，即使允许将用户定义的
	 * 关系移动到那里。带有 pg_catalog 的搜索路径语义现在太混乱。
	 *
	 * 但允许在 pg_catalog 中的关系上创建索引，即使
	 * allow_system_table_mods = off，上层已经保证它是在
	 * 用户定义的关系上，而非系统关系。
	 */
	if (!fc_allow_system_table_mods &&
		((IsCatalogNamespace(fc_relnamespace) && fc_relkind != RELKIND_INDEX) ||
		 IsToastNamespace(fc_relnamespace)) &&
		IsNormalProcessingMode())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied to create \"%s.%s\"",
						get_namespace_name(fc_relnamespace), fc_relname),
				 errdetail("System catalog modifications are currently disallowed.")));

	*fc_relfrozenxid = InvalidTransactionId;
	*fc_relminmxid = InvalidMultiXactId;

	/*
	 * 如果关系类型不支持表空间，则强制 reltablespace 为零。
	 * 这主要是为了整洁。
	 */
	if (!RELKIND_HAS_TABLESPACE(fc_relkind))
		fc_reltablespace = InvalidOid;

	/* 不要为没有物理存储的 relkinds 创建存储。 */
	if (!RELKIND_HAS_STORAGE(fc_relkind))
		fc_create_storage = false;
	else
	{
		/*
		 * 如果调用者未指定 relfilenode，则创建与 relid 相同的
		 * oid 的存储。
		 */
		if (!OidIsValid(fc_relfilenode))
			fc_relfilenode = fc_relid;
	}

	/*
	 * 永远不要允许 pg_class 条目在 reltablespace 中显式指定数据库的
	 * 默认表空间；而是强制为零。这确保如果数据库被克隆到
	 * 不同的默认表空间，pg_class 条目仍然可以与 CREATE DATABASE
	 * 物理复制关系的位置匹配。
	 *
	 * 是的，这有点黑客。
	 */
	if (fc_reltablespace == MyDatabaseTableSpace)
		fc_reltablespace = InvalidOid;

	/*
	 * 构建 relcache 条目。
	 */
	fc_rel = RelationBuildLocalRelation(fc_relname,
									 fc_relnamespace,
									 fc_tupDesc,
									 fc_relid,
									 fc_accessmtd,
									 fc_relfilenode,
									 fc_reltablespace,
									 fc_shared_relation,
									 fc_mapped_relation,
									 fc_relpersistence,
									 fc_relkind);

	
/*
	 * 如果需要，让存储管理器创建关系的磁盘文件。
	 *
	 * 对于表，AM回调会同时创建主档和初始化档。
	 * 对于其他情况，只创建主档；其他档将根据需要创建。
	 */
	if (fc_create_storage)
	{
		if (RELKIND_HAS_TABLE_AM(fc_rel->rd_rel->relkind))
			table_relation_set_new_filenode(fc_rel, &fc_rel->rd_node,
											fc_relpersistence,
											fc_relfrozenxid, fc_relminmxid);
		else if (RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind))
			RelationCreateStorage(fc_rel->rd_node, fc_relpersistence, true);
		else
			Assert(false);
	}

	/*
	 * 如果指定了表空间，通常通过物理文件的存在来保护该表空间的移除；
	 * 对于没有文件的关系，添加一个pg_shdepend条目以进行说明。
	 */
	if (!fc_create_storage && fc_reltablespace != InvalidOid)
		recordDependencyOnTablespace(RelationRelationId, fc_relid,
									 fc_reltablespace);

	/* 确保在事务中止时统计信息被删除 */
	pgstat_create_relation(fc_rel);

	return fc_rel;
}

/* ----------------------------------------------------------------
 *		heap_create_with_catalog		- 创建一个已记录的关系
 *
 *		这分为多个步骤：
 *
 *		1) CheckAttributeNamesTypes() 用于确保元组描述符包含有效的属性名称和类型
 *
 *		2) 打开 pg_class 并使用 get_relname_relid()
 *		   执行扫描以确保没有同名的关系已经存在。
 *
 *		3) 调用 heap_create() 在磁盘上创建新的关系。
 *
 *		4) 调用 TypeCreate() 定义一个与新关系相对应的新类型。
 *
 *		5) 调用 AddNewRelationTuple() 在 pg_class 中注册关系。
 *
 *		6) 调用 AddNewAttributeTuples() 在 pg_attribute 中注册新关系的模式。
 *
 *		7) 调用 StoreConstraints  - vadim 08/22/97
 *
 *		8) 关闭关系并返回新关系的 oid。
 *
 * ----------------------------------------------------------------
 */

/* --------------------------------
 *		CheckAttributeNamesTypes
 *
 *		这个函数用于确保元组描述符包含有效的属性名称和数据类型。
 *		如果有问题，将简单生成 ereport(ERROR)，中止当前事务。
 *
 *		relkind 是要创建的关系的 relkind。
 *		flags 控制允许哪些数据类型，参见 CheckAttributeType。
 * --------------------------------
 */
void CheckAttributeNamesTypes(TupleDesc fc_tupdesc, char fc_relkind,
						 int fc_flags)
{
	int			fc_i;
	int			fc_j;
	int			fc_natts = fc_tupdesc->natts;

	/* 对列数的合法性检查 */
	if (fc_natts < 0 || fc_natts > MaxHeapAttributeNumber)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("tables can have at most %d columns",
						MaxHeapAttributeNumber)));

	/*
	 * 首先检查与系统属性名称的冲突
	 *
	 * 对于视图或类型关系跳过此步骤，因为它们没有系统属性。
	 */
	if (fc_relkind != RELKIND_VIEW && fc_relkind != RELKIND_COMPOSITE_TYPE)
	{
		for (fc_i = 0; fc_i < fc_natts; fc_i++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_i);

			if (SystemAttributeByName(NameStr(fc_attr->attname)) != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_DUPLICATE_COLUMN),
						 errmsg("column name \"%s\" conflicts with a system column name",
								NameStr(fc_attr->attname))));
		}
	}

	/*
	 * 接下来检查重复的属性名称
	 */
	for (fc_i = 1; fc_i < fc_natts; fc_i++)
	{
		for (fc_j = 0; fc_j < fc_i; fc_j++)
		{
			if (strcmp(NameStr(TupleDescAttr(fc_tupdesc, fc_j)->attname),
					   NameStr(TupleDescAttr(fc_tupdesc, fc_i)->attname)) == 0)
				ereport(ERROR,
						(errcode(ERRCODE_DUPLICATE_COLUMN),
						 errmsg("column name \"%s\" specified more than once",
								NameStr(TupleDescAttr(fc_tupdesc, fc_j)->attname))));
		}
	}

	/*
	 * 接下来检查属性类型
	 */
	for (fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		CheckAttributeType(NameStr(TupleDescAttr(fc_tupdesc, fc_i)->attname),
						   TupleDescAttr(fc_tupdesc, fc_i)->atttypid,
						   TupleDescAttr(fc_tupdesc, fc_i)->attcollation,
						   NIL, /* 假设我们正在创建一个新的行类型 */
						   fc_flags);
	}
}

/* --------------------------------
 *		CheckAttributeType
 *
 *		验证属性的提议数据类型是否合法。
 *		这主要是因为在目录中有些类型（和伪类型）
 *		我们不支持作为真实元组的元素。
 *		我们还检查一些表列所需的其他属性。
 *
 * 如果属性被提议添加到现有表或复合类型，请传递一个包含行类型 OID 的单元素列表作为
 * containing_rowtypes。在检查将要创建的行类型时，只需传递 NIL，因为不可能
 * 有对尚不存在的行类型的递归引用。
 *
 * flags 是一个位掩码，用于控制我们允许哪些数据类型。对于大多数情况，
 * 伪类型不被允许作为属性类型，但有一些例外：在某些情况下可以允许
 * ANYARRAYOID、RECORDOID 和 RECORDARRAYOID。 （这之所以可行是因为这些类型类的值
 * 在某种程度上是自我标识的。然而，RECORDOID 和 RECORDARRAYOID 仅在会话内
 * 可可靠辨认，因为身份信息可能使用仅在本地分配的 typmod。调用者应该知道这些情况是否安全。）
 *
 * flags 还可以控制错误消息的措辞。如果指定了 CHKATYPE_IS_PARTKEY，
 * 则“attname”应该作为文本表示分区键列号，而不是实际列名。
 * --------------------------------
 */
void CheckAttributeType(const char *fc_attname,
				   Oid fc_atttypid, Oid fc_attcollation,
				   List *fc_containing_rowtypes,
				   int fc_flags)
{
	char		fc_att_typtype = get_typtype(fc_atttypid);
	Oid			fc_att_typelem;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	if (fc_att_typtype == TYPTYPE_PSEUDO)
	{
		/*
		 * 我们不允许伪类型列，只有在调用者确认那些是可以的情况下，才能例外允许
		 * ANYARRAY、RECORD 和 RECORD[]。
		 *
		 * 我们不需要担心 RECORD 和 RECORD[] 的递归包含，因为 (a) 不应允许任何命名的复合类型
		 * 包含那些，(b) 两个“匿名”的记录类型不能被视为相同类型，因此不可能发生无限递归。
		 */
		if (!((fc_atttypid == ANYARRAYOID && (fc_flags & CHKATYPE_ANYARRAY)) ||
			  (fc_atttypid == RECORDOID && (fc_flags & CHKATYPE_ANYRECORD)) ||
			  (fc_atttypid == RECORDARRAYOID && (fc_flags & CHKATYPE_ANYRECORD))))
		{
			if (fc_flags & CHKATYPE_IS_PARTKEY)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				/* 翻译者：第一个 %s 是整数而不是名称 */
						 errmsg("partition key column %s has pseudo-type %s",
								fc_attname, format_type_be(fc_atttypid))));
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
						 errmsg("column \"%s\" has pseudo-type %s",
								fc_attname, format_type_be(fc_atttypid))));
		}
	}
	else if (fc_att_typtype == TYPTYPE_DOMAIN)
	{
		/*
		 * 如果它是一个域，则递归检查其基本类型。
		 */
		CheckAttributeType(fc_attname, getBaseType(fc_atttypid), fc_attcollation,
						   fc_containing_rowtypes,
						   fc_flags);
	}
	else if (fc_att_typtype == TYPTYPE_COMPOSITE)
	{
		/*
		 * 对于复合类型，递归检查其属性。
		 */
		Relation	fc_relation;
		TupleDesc	fc_tupdesc;
		int			fc_i;

		/*
		 * 检查自包含性。最终我们可能能够允许
		 * 这个（如果可以，就不需要抱怨地返回）但尚不清楚有多少其他地方会需要
		 * 反递归防御，才能安全地允许表包含其自己的行类型。
		 */
		if (list_member_oid(fc_containing_rowtypes, fc_atttypid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("composite type %s cannot be made a member of itself",
							format_type_be(fc_atttypid))));

		fc_containing_rowtypes = lappend_oid(fc_containing_rowtypes, fc_atttypid);

		fc_relation = relation_open(get_typ_typrelid(fc_atttypid), AccessShareLock);

		fc_tupdesc = RelationGetDescr(fc_relation);

		for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_i);

			if (fc_attr->attisdropped)
				continue;
			CheckAttributeType(NameStr(fc_attr->attname),
							   fc_attr->atttypid, fc_attr->attcollation,
							   fc_containing_rowtypes,
							   fc_flags & ~CHKATYPE_IS_PARTKEY);
		}

		relation_close(fc_relation, AccessShareLock);

		fc_containing_rowtypes = list_delete_last(fc_containing_rowtypes);
	}
	else if (fc_att_typtype == TYPTYPE_RANGE)
	{
		/*
		 * 如果它是一个范围，则递归检查其子类型。
		 */
		CheckAttributeType(fc_attname, get_range_subtype(fc_atttypid),
						   get_range_collation(fc_atttypid),
						   fc_containing_rowtypes,
						   fc_flags);
	}
	else if (OidIsValid((fc_att_typelem = get_element_type(fc_atttypid))))
	{
		/*
		 * 也必须递归检查数组类型，以防它们是复合的。
		 */
		CheckAttributeType(fc_attname, fc_att_typelem, fc_attcollation,
						   fc_containing_rowtypes,
						   fc_flags);
	}

	/*
	 * 这在 SQL 标准下可能不严格无效，但它相当
	 * 无用，并且无法被转储，所以我们必须禁止它。
	 */
	if (!OidIsValid(fc_attcollation) && type_is_collatable(fc_atttypid))
	{
		if (fc_flags & CHKATYPE_IS_PARTKEY)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
			/* 翻译者：第一个 %s 是整数而不是名称 */
					 errmsg("no collation was derived for partition key column %s with collatable type %s",
							fc_attname, format_type_be(fc_atttypid)),
					 errhint("Use the COLLATE clause to set the collation explicitly.")));
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
					 errmsg("no collation was derived for column \"%s\" with collatable type %s",
							fc_attname, format_type_be(fc_atttypid)),
					 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}
}

/*
 * InsertPgAttributeTuples
 *		构建并插入一组元组到 pg_attribute。
 *
 * 调用者已打开并锁定 pg_attribute。tupdesc 包含要插入的
 * 属性。attcacheoff 总是初始化为 -1。attoptions
 * 提供 attoptions 字段的值，并且必须包含与 tupdesc 相同
 * 数量的元素，或者为 NULL。pg_attribute 的其他可变长度字段
 * 始终初始化为 null 值。
 *
 * indstate 是 CatalogTupleInsertWithInfo 的索引状态。可以
 * 传递为 NULL，在这种情况下我们将获取必要的信息。
 * （插入多个属性时不要这样做，因为这稍微贵一些。）
 *
 * new_rel_oid 是分配给插入属性的关系 OID。
 * 如果设置为 InvalidOid，则使用 tupdesc 的关系 OID。
 */
void InsertPgAttributeTuples(Relation pg_attribute_rel,
						TupleDesc fc_tupdesc,
						Oid fc_new_rel_oid,
						Datum *fc_attoptions,
						CatalogIndexState fc_indstate)
{
	TupleTableSlot **fc_slot;
	TupleDesc	fc_td;
	int			fc_nslots;
	int			fc_natts = 0;
	int			fc_slotCount = 0;
	bool		fc_close_index = false;

	fc_td = RelationGetDescr(pg_attribute_rel);

	/* 初始化要使用的槽的数量 */
	fc_nslots = Min(fc_tupdesc->natts,
				 (MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_attribute)));
	fc_slot = palloc(sizeof(TupleTableSlot *) * fc_nslots);
	for (int fc_i = 0; fc_i < fc_nslots; fc_i++)
		fc_slot[fc_i] = MakeSingleTupleTableSlot(fc_td, &TTSOpsHeapTuple);

	while (fc_natts < fc_tupdesc->natts)
	{
		Form_pg_attribute fc_attrs = TupleDescAttr(fc_tupdesc, fc_natts);

		ExecClearTuple(fc_slot[fc_slotCount]);

		memset(fc_slot[fc_slotCount]->tts_isnull, false,
			   fc_slot[fc_slotCount]->tts_tupleDescriptor->natts * sizeof(bool));

		if (fc_new_rel_oid != InvalidOid)
			fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(fc_new_rel_oid);
		else
			fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attrelid - 1] = ObjectIdGetDatum(fc_attrs->attrelid);

		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attname - 1] = NameGetDatum(&fc_attrs->attname);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_atttypid - 1] = ObjectIdGetDatum(fc_attrs->atttypid);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(fc_attrs->attstattarget);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(fc_attrs->attlen);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(fc_attrs->attnum);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(fc_attrs->attndims);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(-1);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(fc_attrs->atttypmod);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attbyval - 1] = BoolGetDatum(fc_attrs->attbyval);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attalign - 1] = CharGetDatum(fc_attrs->attalign);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attstorage - 1] = CharGetDatum(fc_attrs->attstorage);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attcompression - 1] = CharGetDatum(fc_attrs->attcompression);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(fc_attrs->attnotnull);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(fc_attrs->atthasdef);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(fc_attrs->atthasmissing);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(fc_attrs->attidentity);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(fc_attrs->attgenerated);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(fc_attrs->attisdropped);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(fc_attrs->attislocal);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(fc_attrs->attinhcount);
		fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(fc_attrs->attcollation);
		if (fc_attoptions && fc_attoptions[fc_natts] != (Datum) 0)
			fc_slot[fc_slotCount]->tts_values[Anum_pg_attribute_attoptions - 1] = fc_attoptions[fc_natts];
		else
			fc_slot[fc_slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;

		/* 先从空权限和空选项开始 */
		fc_slot[fc_slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
		fc_slot[fc_slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
		fc_slot[fc_slotCount]->tts_isnull[Anum_pg_attribute_attmissingval - 1] = true;

		ExecStoreVirtualTuple(fc_slot[fc_slotCount]);
		fc_slotCount++;

		/*
		 * 如果槽已满或处理已达到结束，插入
		 * 一批元组。
		 */
		if (fc_slotCount == fc_nslots || fc_natts == fc_tupdesc->natts - 1)
		{
			/* 仅在我们知道需要时获取索引信息 */
			if (!fc_indstate)
			{
				fc_indstate = CatalogOpenIndexes(pg_attribute_rel);
				fc_close_index = true;
			}

			/* 插入新的元组并更新索引 */
			CatalogTuplesMultiInsertWithInfo(pg_attribute_rel, fc_slot, fc_slotCount,
											 fc_indstate);
			fc_slotCount = 0;
		}

		fc_natts++;
	}

	if (fc_close_index)
		CatalogCloseIndexes(fc_indstate);
	for (int fc_i = 0; fc_i < fc_nslots; fc_i++)
		ExecDropSingleTupleTableSlot(fc_slot[fc_i]);
	pfree(fc_slot);
}

/* --------------------------------
 *		AddNewAttributeTuples
 *
 *		通过向 pg_attribute 添加元组来注册新关系的模式。
 * --------------------------------
 */
static void fc_AddNewAttributeTuples(Oid fc_new_rel_oid,
					  TupleDesc fc_tupdesc,
					  char fc_relkind)
{
	Relation	fc_rel;
	CatalogIndexState fc_indstate;
	int			fc_natts = fc_tupdesc->natts;
	ObjectAddress fc_myself,
				fc_referenced;

	/*
	 * 打开 pg_attribute 及其索引。
	 */
	fc_rel = table_open(AttributeRelationId, RowExclusiveLock);

	fc_indstate = CatalogOpenIndexes(fc_rel);

	/* 将统计详细级别设置为合理的默认值 */
	for (int fc_i = 0; fc_i < fc_natts; fc_i++)
		fc_tupdesc->attrs[fc_i].attstattarget = -1;
	InsertPgAttributeTuples(fc_rel, fc_tupdesc, fc_new_rel_oid, NULL, fc_indstate);

	/* 添加对其数据类型和排序规则的依赖 */
	for (int fc_i = 0; fc_i < fc_natts; fc_i++)
	{
		/* 添加依赖信息 */
		ObjectAddressSubSet(fc_myself, RelationRelationId, fc_new_rel_oid, fc_i + 1);
		ObjectAddressSet(fc_referenced, TypeRelationId,
						 fc_tupdesc->attrs[fc_i].atttypid);
		recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);

		/* 默认排序规则是固定的，因此不必记录它 */
		if (OidIsValid(fc_tupdesc->attrs[fc_i].attcollation) &&
			fc_tupdesc->attrs[fc_i].attcollation != DEFAULT_COLLATION_OID)
		{
			ObjectAddressSet(fc_referenced, CollationRelationId,
							 fc_tupdesc->attrs[fc_i].attcollation);
			recordDependencyOn(&fc_myself, &fc_referenced, DEPENDENCY_NORMAL);
		}
	}

	
/*
	 * 接下来我们添加系统属性。如果关系没有 OID，则跳过 OID。对于视图或类型关系，全部跳过。
	 * 我们在这里不处理数据类型依赖关系，因为大概这些类型都是固定的。
	 */
	if (fc_relkind != RELKIND_VIEW && fc_relkind != RELKIND_COMPOSITE_TYPE)
	{
		TupleDesc	fc_td;

		fc_td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);

		InsertPgAttributeTuples(fc_rel, fc_td, fc_new_rel_oid, NULL, fc_indstate);
		FreeTupleDesc(fc_td);
	}

	/*
	 * 清理
	 */
	CatalogCloseIndexes(fc_indstate);

	table_close(fc_rel, RowExclusiveLock);
}

/* --------------------------------
 *		InsertPgClassTuple
 *
 *		在 pg_class 中构造并插入一个新元组。
 *
 * 调用者已经打开并锁定 pg_class。
 * 元组数据来自 new_rel_desc->rd_rel，除了在缓存的 reldesc 中不存在的
 * 可变宽度字段。
 * relacl 和 reloptions 以 Datum 形式传入（以避免引用 heap.h 中的数据类型）。
 * 传入 (Datum) 0 将它们设置为 NULL。
 * --------------------------------
 */
void InsertPgClassTuple(Relation pg_class_desc,
				   Relation fc_new_rel_desc,
				   Oid fc_new_rel_oid,
				   Datum fc_relacl,
				   Datum fc_reloptions)
{
	Form_pg_class fc_rd_rel = fc_new_rel_desc->rd_rel;
	Datum		fc_values[Natts_pg_class];
	bool		fc_nulls[Natts_pg_class];
	HeapTuple	fc_tup;

	/* 这有点繁琐，但比我们以前的做法清晰多了... */
	memset(fc_values, 0, sizeof(fc_values));
	memset(fc_nulls, false, sizeof(fc_nulls));

	fc_values[Anum_pg_class_oid - 1] = ObjectIdGetDatum(fc_new_rel_oid);
	fc_values[Anum_pg_class_relname - 1] = NameGetDatum(&fc_rd_rel->relname);
	fc_values[Anum_pg_class_relnamespace - 1] = ObjectIdGetDatum(fc_rd_rel->relnamespace);
	fc_values[Anum_pg_class_reltype - 1] = ObjectIdGetDatum(fc_rd_rel->reltype);
	fc_values[Anum_pg_class_reloftype - 1] = ObjectIdGetDatum(fc_rd_rel->reloftype);
	fc_values[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(fc_rd_rel->relowner);
	fc_values[Anum_pg_class_relam - 1] = ObjectIdGetDatum(fc_rd_rel->relam);
	fc_values[Anum_pg_class_relfilenode - 1] = ObjectIdGetDatum(fc_rd_rel->relfilenode);
	fc_values[Anum_pg_class_reltablespace - 1] = ObjectIdGetDatum(fc_rd_rel->reltablespace);
	fc_values[Anum_pg_class_relpages - 1] = Int32GetDatum(fc_rd_rel->relpages);
	fc_values[Anum_pg_class_reltuples - 1] = Float4GetDatum(fc_rd_rel->reltuples);
	fc_values[Anum_pg_class_relallvisible - 1] = Int32GetDatum(fc_rd_rel->relallvisible);
	fc_values[Anum_pg_class_reltoastrelid - 1] = ObjectIdGetDatum(fc_rd_rel->reltoastrelid);
	fc_values[Anum_pg_class_relhasindex - 1] = BoolGetDatum(fc_rd_rel->relhasindex);
	fc_values[Anum_pg_class_relisshared - 1] = BoolGetDatum(fc_rd_rel->relisshared);
	fc_values[Anum_pg_class_relpersistence - 1] = CharGetDatum(fc_rd_rel->relpersistence);
	fc_values[Anum_pg_class_relkind - 1] = CharGetDatum(fc_rd_rel->relkind);
	fc_values[Anum_pg_class_relnatts - 1] = Int16GetDatum(fc_rd_rel->relnatts);
	fc_values[Anum_pg_class_relchecks - 1] = Int16GetDatum(fc_rd_rel->relchecks);
	fc_values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(fc_rd_rel->relhasrules);
	fc_values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(fc_rd_rel->relhastriggers);
	fc_values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(fc_rd_rel->relrowsecurity);
	fc_values[Anum_pg_class_relforcerowsecurity - 1] = BoolGetDatum(fc_rd_rel->relforcerowsecurity);
	fc_values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(fc_rd_rel->relhassubclass);
	fc_values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(fc_rd_rel->relispopulated);
	fc_values[Anum_pg_class_relreplident - 1] = CharGetDatum(fc_rd_rel->relreplident);
	fc_values[Anum_pg_class_relispartition - 1] = BoolGetDatum(fc_rd_rel->relispartition);
	fc_values[Anum_pg_class_relrewrite - 1] = ObjectIdGetDatum(fc_rd_rel->relrewrite);
	fc_values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(fc_rd_rel->relfrozenxid);
	fc_values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(fc_rd_rel->relminmxid);
	if (fc_relacl != (Datum) 0)
		fc_values[Anum_pg_class_relacl - 1] = fc_relacl;
	else
		fc_nulls[Anum_pg_class_relacl - 1] = true;
	if (fc_reloptions != (Datum) 0)
		fc_values[Anum_pg_class_reloptions - 1] = fc_reloptions;
	else
		fc_nulls[Anum_pg_class_reloptions - 1] = true;

	/* 如果需要，relpartbound 通过更新该元组设置 */
	fc_nulls[Anum_pg_class_relpartbound - 1] = true;

	fc_tup = heap_form_tuple(RelationGetDescr(pg_class_desc), fc_values, fc_nulls);

	/* 最后插入新的元组，更新索引，并清理 */
	CatalogTupleInsert(pg_class_desc, fc_tup);

	heap_freetuple(fc_tup);
}

/* --------------------------------
 *		AddNewRelationTuple
 *
 *		这将通过向pg_class添加一个元组来注册新关系到目录中。
 * --------------------------------
 */
static void fc_AddNewRelationTuple(Relation pg_class_desc,
					Relation fc_new_rel_desc,
					Oid fc_new_rel_oid,
					Oid fc_new_type_oid,
					Oid fc_reloftype,
					Oid fc_relowner,
					char fc_relkind,
					TransactionId fc_relfrozenxid,
					TransactionId fc_relminmxid,
					Datum fc_relacl,
					Datum fc_reloptions)
{
	Form_pg_class fc_new_rel_reltup;

	/*
	 * 首先我们更新一些信息在我们的未编目关系的
	 * 关系描述符中。
	 */
	fc_new_rel_reltup = fc_new_rel_desc->rd_rel;

	/* 关系是空的 */
	fc_new_rel_reltup->relpages = 0;
	fc_new_rel_reltup->reltuples = -1;
	fc_new_rel_reltup->relallvisible = 0;

	/* 序列总是有已知的大小 */
	if (fc_relkind == RELKIND_SEQUENCE)
	{
		fc_new_rel_reltup->relpages = 1;
		fc_new_rel_reltup->reltuples = 1;
	}

	fc_new_rel_reltup->relfrozenxid = fc_relfrozenxid;
	fc_new_rel_reltup->relminmxid = fc_relminmxid;
	fc_new_rel_reltup->relowner = fc_relowner;
	fc_new_rel_reltup->reltype = fc_new_type_oid;
	fc_new_rel_reltup->reloftype = fc_reloftype;

	/* relispartition总是通过稍后更新此元组来设置 */
	fc_new_rel_reltup->relispartition = false;

	/* 即使reltype为零，也要用合理的东西填充rd_att的类型ID */
	fc_new_rel_desc->rd_att->tdtypeid = fc_new_type_oid ? fc_new_type_oid : RECORDOID;
	fc_new_rel_desc->rd_att->tdtypmod = -1;

	/* 现在构建并插入元组 */
	InsertPgClassTuple(pg_class_desc, fc_new_rel_desc, fc_new_rel_oid,
					   fc_relacl, fc_reloptions);
}


/* --------------------------------
 *		AddNewRelationType -
 *
 *		定义一个与新关系相对应的复合类型
 * --------------------------------
 */
static ObjectAddress fc_AddNewRelationType(const char *fc_typeName,
				   Oid fc_typeNamespace,
				   Oid fc_new_rel_oid,
				   char fc_new_rel_kind,
				   Oid fc_ownerid,
				   Oid fc_new_row_type,
				   Oid fc_new_array_type)
{
	return
		TypeCreate(fc_new_row_type,	/* 可选的预定OID */
				   fc_typeName,	/* 类型名称 */
				   fc_typeNamespace,	/* 类型命名空间 */
				   fc_new_rel_oid, /* 关系oid */
				   fc_new_rel_kind,	/* 关系类型 */
				   fc_ownerid,		/* 所有者的 ID */
				   -1,			/* 内部大小 (varlena) */
				   TYPTYPE_COMPOSITE,	/* 类型-类型 (复合) */
				   TYPCATEGORY_COMPOSITE,	/* 类型-类别 (同前) */
				   false,		/* 复合类型从不被优先选择 */
				   DEFAULT_TYPDELIM,	/* 默认数组分隔符 */
				   F_RECORD_IN, /* 输入过程 */
				   F_RECORD_OUT,	/* 输出过程 */
				   F_RECORD_RECV,	/* 接收过程 */
				   F_RECORD_SEND,	/* 发送过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   InvalidOid,	/* 分析过程 - 默认 */
				   InvalidOid,	/* 下标过程 - 无 */
				   InvalidOid,	/* 数组元素类型 - 无关紧要 */
				   false,		/* 这不是一个数组类型 */
				   fc_new_array_type,	/* 如果有的话，数组类型 */
				   InvalidOid,	/* 域基本类型 - 无关紧要 */
				   NULL,		/* 默认值 - 无 */
				   NULL,		/* 默认二进制表示 */
				   false,		/* 通过引用传递 */
				   TYPALIGN_DOUBLE, /* 对齐 - 必须是最大的！ */
				   TYPSTORAGE_EXTENDED, /* 完全可TOAST */
				   -1,			/* typmod */
				   0,			/* typBaseType的数组维度 */
				   false,		/* 类型不为 NULL */
				   InvalidOid); /* 行类型从不具有排序规则 */
}

/* --------------------------------
 *		heap_create_with_catalog
 *
 *		创建一个新的编目关系。请参阅上面的注释。
 *
 * 参数：
 *	relname: 要赋予新关系的名称
 *	relnamespace: 它所在命名空间的OID
 *	reltablespace: 它所在表空间的OID
 *	relid: 分配给新关系的OID，或InvalidOid以选择一个新的OID
 *	reltypeid: 分配给关系的行类型的OID，或InvalidOid以选择一个
 *	reloftypeid: 如果是有类型的表，则为基础类型的OID；否则InvalidOid
 *	ownerid: 新关系的所有者OID
 *	accessmtd: 新关系的访问方法OID
 *	tupdesc: 元组描述符（列定义的来源）
 *	cooked_constraints: 预先处理的检查约束和默认值列表
 *	relkind: 新关系的relkind
 *	relpersistence: 关系的持久性状态（永久、临时或未记录）
 *	shared_relation: 如果这是一个共享关系，则为true
 *	mapped_relation: 如果关系将使用relfilenode映射，则为true
 *	oncommit: 在提交标记（仅在临时表时相关）
 *	reloptions: 以Datum形式表示的reloptions，如果没有则为(Datum) 0
 *	use_user_acl: 如果应该查找用户定义的默认权限，则为true；
 *		如果为false，则relacl始终设置为NULL
 *	allow_system_table_mods: true以允许在系统命名空间中创建
 *	is_internal: 这是一个系统生成的目录吗？
 *
 * 输出参数：
 *	typaddress: 如果不为null，则获取新pg_type条目的对象地址
 *	（如果relkind是一个不获得pg_type条目的类型，则必须为null）
 *
 * 返回新关系的OID
 * --------------------------------
 */
Oid heap_create_with_catalog(const char *fc_relname,
						 Oid fc_relnamespace,
						 Oid fc_reltablespace,
						 Oid fc_relid,
						 Oid fc_reltypeid,
						 Oid fc_reloftypeid,
						 Oid fc_ownerid,
						 Oid fc_accessmtd,
						 TupleDesc fc_tupdesc,
						 List *fc_cooked_constraints,
						 char fc_relkind,
						 char fc_relpersistence,
						 bool fc_shared_relation,
						 bool fc_mapped_relation,
						 OnCommitAction fc_oncommit,
						 Datum fc_reloptions,
						 bool fc_use_user_acl,
						 bool fc_allow_system_table_mods,
						 bool fc_is_internal,
						 Oid fc_relrewrite,
						 ObjectAddress *fc_typaddress)
{
	Relation	pg_class_desc;
	Relation	fc_new_rel_desc;
	Acl		   *fc_relacl;
	Oid			fc_existing_relid;
	Oid			fc_old_type_oid;
	Oid			fc_new_type_oid;

	/* 默认设置为InvalidOid，除非通过二进制升级覆盖 */
	Oid			fc_relfilenode = InvalidOid;
	TransactionId fc_relfrozenxid;
	MultiXactId fc_relminmxid;

	pg_class_desc = table_open(RelationRelationId, RowExclusiveLock);

	/*
	 * 健康检查
	 */
	Assert(IsNormalProcessingMode() || IsBootstrapProcessingMode());

	/*
	 * 验证所提议的tupdesc以满足所需的relkind。如果
	 * allow_system_table_mods被启用，允许使用ANYARRAY；这是一个
	 * 黑客以允许在VACUUM FULL期间创建pg_statistic并克隆它。
	 */
	CheckAttributeNamesTypes(fc_tupdesc, fc_relkind,
							 fc_allow_system_table_mods ? CHKATYPE_ANYARRAY : 0);

	/*
	 * 如果关系已经存在，这将会在后面失败。但在这里捕获它，我们可以发出更友好的错误信息。
	 */
	fc_existing_relid = get_relname_relid(fc_relname, fc_relnamespace);
	if (fc_existing_relid != InvalidOid)
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_TABLE),
				 errmsg("relation \"%s\" already exists", fc_relname)));

	/*
	 * 由于我们将创建一个行类型，因此还要检查与现有类型名称的冲突。如果存在且它是一个自动生成的数组，我们可以将其重命名；否则我们至少可以给出一个好的错误信息。
	 */
	fc_old_type_oid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
								   CStringGetDatum(fc_relname),
								   ObjectIdGetDatum(fc_relnamespace));
	if (OidIsValid(fc_old_type_oid))
	{
		if (!moveArrayTypeName(fc_old_type_oid, fc_relname, fc_relnamespace))
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("type \"%s\" already exists", fc_relname),
					 errhint("A relation has an associated type of the same name, "
							 "so you must use a name that doesn't conflict "
							 "with any existing type.")));
	}

	/*
	 * 共享关系必须在 pg_global 中（最后的检查）
	 */
	if (fc_shared_relation && fc_reltablespace != GLOBALTABLESPACE_OID)
		elog(ERROR, "shared relations must be placed in pg_global tablespace");

	/*
	 * 为关系分配一个 OID，除非我们被告知要使用什么。
	 *
	 * OID 也将是 relfilenode，因此确保它不会与 pg_class 的 OID 或现有物理文件冲突。
	 */
	if (!OidIsValid(fc_relid))
	{
		/* 对 pg_class.oid 和 relfilenode 使用二进制升级覆盖 */
		if (IsBinaryUpgrade)
		{
			/*
			 * 在这里不支持索引；它们使用 binary_upgrade_next_index_pg_class_oid。
			 */
			Assert(fc_relkind != RELKIND_INDEX);
			Assert(fc_relkind != RELKIND_PARTITIONED_INDEX);

			if (fc_relkind == RELKIND_TOASTVALUE)
			{
				/* 可能没有 TOAST 表，因此我们必须对此进行测试。 */
				if (OidIsValid(binary_upgrade_next_toast_pg_class_oid))
				{
					fc_relid = binary_upgrade_next_toast_pg_class_oid;
					binary_upgrade_next_toast_pg_class_oid = InvalidOid;

					if (!OidIsValid(binary_upgrade_next_toast_pg_class_relfilenode))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
								 errmsg("toast relfilenode value not set when in binary upgrade mode")));

					fc_relfilenode = binary_upgrade_next_toast_pg_class_relfilenode;
					binary_upgrade_next_toast_pg_class_relfilenode = InvalidOid;
				}
			}
			else
			{
				if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("pg_class heap OID value not set when in binary upgrade mode")));

				fc_relid = binary_upgrade_next_heap_pg_class_oid;
				binary_upgrade_next_heap_pg_class_oid = InvalidOid;

				if (RELKIND_HAS_STORAGE(fc_relkind))
				{
					if (!OidIsValid(binary_upgrade_next_heap_pg_class_relfilenode))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
								 errmsg("relfilenode value not set when in binary upgrade mode")));

					fc_relfilenode = binary_upgrade_next_heap_pg_class_relfilenode;
					binary_upgrade_next_heap_pg_class_relfilenode = InvalidOid;
				}
			}
		}

		if (!OidIsValid(fc_relid))
			fc_relid = GetNewRelFileNode(fc_reltablespace, pg_class_desc,
									  fc_relpersistence);
	}

	/*
	 * 其他会话的目录扫描在我们提交之前无法找到这个。因此，持有 AccessExclusiveLock 没有坏处。这里执行，以便调用者不会意外地在其锁模式或获取时机上有所不同。
	 */
	LockRelationOid(fc_relid, AccessExclusiveLock);

	/*
	 * 确定关系的初始权限。
	 */
	if (fc_use_user_acl)
	{
		switch (fc_relkind)
		{
			case RELKIND_RELATION:
			case RELKIND_VIEW:
			case RELKIND_MATVIEW:
			case RELKIND_FOREIGN_TABLE:
			case RELKIND_PARTITIONED_TABLE:
				fc_relacl = get_user_default_acl(OBJECT_TABLE, fc_ownerid,
											  fc_relnamespace);
				break;
			case RELKIND_SEQUENCE:
				fc_relacl = get_user_default_acl(OBJECT_SEQUENCE, fc_ownerid,
											  fc_relnamespace);
				break;
			default:
				fc_relacl = NULL;
				break;
		}
	}
	else
		fc_relacl = NULL;

	/*
	 * 创建 relcache 条目（此时大部分是虚拟的）和物理磁盘文件。 （如果我们在后面失败，smgr 有责任再次删除磁盘文件。）
	 *
	 * 注意：即使对于二进制升级，传递 create_storage = true 也是正确的。我们在这里创建的存储将在稍后被替换，但在此期间我们需要在磁盘上有一些东西。
	 */
	fc_new_rel_desc = heap_create(fc_relname,
							   fc_relnamespace,
							   fc_reltablespace,
							   fc_relid,
							   fc_relfilenode,
							   fc_accessmtd,
							   fc_tupdesc,
							   fc_relkind,
							   fc_relpersistence,
							   fc_shared_relation,
							   fc_mapped_relation,
							   fc_allow_system_table_mods,
							   &fc_relfrozenxid,
							   &fc_relminmxid,
							   true);

	Assert(fc_relid == RelationGetRelid(fc_new_rel_desc));

	fc_new_rel_desc->rd_rel->relrewrite = fc_relrewrite;

	/*
	 * 决定是否为关系的行类型创建一个 pg_type 条目。 这些类型会被创建，除非将关系用作实现细节：toast 表、序列和索引。
	 */
	if (!(fc_relkind == RELKIND_SEQUENCE ||
		  fc_relkind == RELKIND_TOASTVALUE ||
		  fc_relkind == RELKIND_INDEX ||
		  fc_relkind == RELKIND_PARTITIONED_INDEX))
	{
		Oid			fc_new_array_oid;
		ObjectAddress fc_new_type_addr;
		char	   *fc_relarrayname;

		/*
		 * 我们也会对复合类型创建一个数组。由于历史原因，数组类型的 OID 首先被分配。
		 */
		fc_new_array_oid = AssignTypeArrayOid();

		/*
		 * 为复合类型创建 pg_type 条目。复合类型的 OID 可以由调用者预先选择，但如果 reltypeid 是 InvalidOid，我们将为其生成一个新 OID。
		 *
		 * 注意：在这里我们可能会遇到唯一索引失败的情况，如果有人在并行创建相同的类型名称，但在我们检查重复名称时尚未提交。
		 */
		fc_new_type_addr = fc_AddNewRelationType(fc_relname,
										   fc_relnamespace,
										   fc_relid,
										   fc_relkind,
										   fc_ownerid,
										   fc_reltypeid,
										   fc_new_array_oid);
		fc_new_type_oid = fc_new_type_addr.objectId;
		if (fc_typaddress)
			*fc_typaddress = fc_new_type_addr;

		/* 现在创建数组类型。 */
		fc_relarrayname = makeArrayTypeName(fc_relname, fc_relnamespace);

		TypeCreate(fc_new_array_oid,	/* 强制将类型的 OID 设置为此 */
				   fc_relarrayname,	/* 数组类型名称 */
				   fc_relnamespace,	/* 与父类相同的命名空间 */
				   InvalidOid,	/* 不是复合类型，没有 relationOid */
				   0,			/* relkind，这里也不适用 */
				   fc_ownerid,		/* 所有者的 ID */
				   -1,			/* 内部大小（varlena） */
				   TYPTYPE_BASE,	/* 不是复合类型 - typelem 是 */
				   TYPCATEGORY_ARRAY,	/* 类型类别（数组） */
				   false,		/* 数组类型从不优选 */
				   DEFAULT_TYPDELIM,	/* 默认数组分隔符 */
				   F_ARRAY_IN,	/* 数组输入过程 */
				   F_ARRAY_OUT, /* 数组输出过程 */
				   F_ARRAY_RECV,	/* 数组接收（bin）过程 */
				   F_ARRAY_SEND,	/* 数组发送（bin）过程 */
				   InvalidOid,	/* typmodin 过程 - 无 */
				   InvalidOid,	/* typmodout 过程 - 无 */
				   F_ARRAY_TYPANALYZE,	/* 数组分析程序 */
				   F_ARRAY_SUBSCRIPT_HANDLER,	/* 数组下标过程 */
				   fc_new_type_oid,	/* 数组元素类型 - 行类型 */
				   true,		/* 是的，这是一种数组类型 */
				   InvalidOid,	/* 这没有数组类型 */
				   InvalidOid,	/* 域基本类型 - 无关紧要 */
				   NULL,		/* 默认值 - 无 */
				   NULL,		/* 默认二进制表示 */
				   false,		/* 通过引用传递 */
				   TYPALIGN_DOUBLE, /* 对齐 - 必须是最大的！ */
				   TYPSTORAGE_EXTENDED, /* 完全可TOAST */
				   -1,			/* typmod */
				   0,			/* typBaseType的数组维度 */
				   false,		/* 类型不为 NULL */
				   InvalidOid); /* 行类型从不具有排序规则 */

		pfree(fc_relarrayname);
	}
	else
	{
		/* 调用者不应该期望创建类型。 */
		Assert(fc_reltypeid == InvalidOid);
		Assert(fc_typaddress == NULL);

		fc_new_type_oid = InvalidOid;
	}

	/*
	 * 现在为关系在 pg_class 中创建一个条目。
	 *
	 * 注意：如果有人正在并行创建相同的关系名称，但在我们检查重复名称时尚未提交，
	 * 我们可能会在这里遇到唯一索引失败。
	 */
	fc_AddNewRelationTuple(pg_class_desc,
						fc_new_rel_desc,
						fc_relid,
						fc_new_type_oid,
						fc_reloftypeid,
						fc_ownerid,
						fc_relkind,
						fc_relfrozenxid,
						fc_relminmxid,
						PointerGetDatum(fc_relacl),
						fc_reloptions);

	/*
	 * 现在向 pg_attribute 添加属性元组到我们新的关系中。
	 */
	fc_AddNewAttributeTuples(fc_relid, fc_new_rel_desc->rd_att, fc_relkind);

	/*
	 * 创建一个依赖链接，以强制在其命名空间被删除时删除关系。
	 * 还为其所有者创建一个依赖链接，以及为默认 ACL 中提到的任何角色创建依赖。
	 *
	 * 对于复合类型，这些依赖关系在 pg_type 条目中被跟踪，因此我们不需要在这里记录它们。
	 * 同样，TOAST 表不需要命名空间依赖（它们存在于一个固定的命名空间中），
	 * 也不需要所有者依赖（它们通过父表间接依赖），
	 * 也不应该拥有任何 ACL 条目。扩展依赖的情况也是如此。
	 *
	 * 此外，在引导模式下跳过此步骤，因为在引导时我们不会创建依赖。
	 */
	if (fc_relkind != RELKIND_COMPOSITE_TYPE &&
		fc_relkind != RELKIND_TOASTVALUE &&
		!IsBootstrapProcessingMode())
	{
		ObjectAddress fc_myself,
					fc_referenced;
		ObjectAddresses *fc_addrs;

		ObjectAddressSet(fc_myself, RelationRelationId, fc_relid);

		recordDependencyOnOwner(RelationRelationId, fc_relid, fc_ownerid);

		recordDependencyOnNewAcl(RelationRelationId, fc_relid, 0, fc_ownerid, fc_relacl);

		recordDependencyOnCurrentExtension(&fc_myself, false);

		fc_addrs = new_object_addresses();

		ObjectAddressSet(fc_referenced, NamespaceRelationId, fc_relnamespace);
		add_exact_object_address(&fc_referenced, fc_addrs);

		if (fc_reloftypeid)
		{
			ObjectAddressSet(fc_referenced, TypeRelationId, fc_reloftypeid);
			add_exact_object_address(&fc_referenced, fc_addrs);
		}

		/*
		 * 创建一个依赖链接，以强制在其访问方法被删除时删除关系。
		 *
		 * 不需要为 toast 表添加显式依赖，因为主表依赖于它。
		 */
		if (RELKIND_HAS_TABLE_AM(fc_relkind) && fc_relkind != RELKIND_TOASTVALUE)
		{
			ObjectAddressSet(fc_referenced, AccessMethodRelationId, fc_accessmtd);
			add_exact_object_address(&fc_referenced, fc_addrs);
		}

		record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
		free_object_addresses(fc_addrs);
	}

	/* 新关系的创建后钩子 */
	InvokeObjectPostCreateHookArg(RelationRelationId, fc_relid, 0, fc_is_internal);

	/*
	 * 存储任何提供的约束和默认值。
	 *
	 * 注意：这可能会进行 CommandCounterIncrement 并重建 relcache 条目，
	 * 因此此时关系必须是有效且自洽的。
	 * 特别是，尚未在任何地方存在约束和默认值。
	 */
	fc_StoreConstraints(fc_new_rel_desc, fc_cooked_constraints, fc_is_internal);

	/*
	 * 如果有特殊的提交时操作，请记住它
	 */
	if (fc_oncommit != ONCOMMIT_NOOP)
		register_on_commit_action(fc_relid, fc_oncommit);

	/*
	 * 好的，关系已被编目，因此关闭我们的关系并返回新创建关系的 OID。
	 */
	table_close(fc_new_rel_desc, NoLock);	/* 在事务结束之前不要解锁 */
	table_close(pg_class_desc, RowExclusiveLock);

	return fc_relid;
}

/*
 *		RelationRemoveInheritance
 *
 * 以前，这个例程检查子关系，并在发现任何子关系时中止删除。
 * 现在我们依赖于依赖机制来检查或删除子关系。
 * 到我们抵达这里时，没有子关系，我们只需要删除任何 pg_inherits 行
 * 将此关系与其父级链接。
 */
static void fc_RelationRemoveInheritance(Oid fc_relid)
{
	Relation	fc_catalogRelation;
	SysScanDesc fc_scan;
	ScanKeyData fc_key;
	HeapTuple	fc_tuple;

	fc_catalogRelation = table_open(InheritsRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key,
				Anum_pg_inherits_inhrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));

	fc_scan = systable_beginscan(fc_catalogRelation, InheritsRelidSeqnoIndexId, true,
							  NULL, 1, &fc_key);

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		CatalogTupleDelete(fc_catalogRelation, &fc_tuple->t_self);

	systable_endscan(fc_scan);
	table_close(fc_catalogRelation, RowExclusiveLock);
}

/*
 *		DeleteRelationTuple
 *
 * 移除给定 relid 的 pg_class 行。
 *
 * 注意：这由关系删除和索引删除共享。
 * 不打算在其他地方使用。
 */
void DeleteRelationTuple(Oid fc_relid)
{
	Relation	pg_class_desc;
	HeapTuple	fc_tup;

	/* 在 pg_class 关系上获取适当的锁 */
	pg_class_desc = table_open(RelationRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tup))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);

	/* 从 pg_class 中删除关系元组，并结束操作 */
	CatalogTupleDelete(pg_class_desc, &fc_tup->t_self);

	ReleaseSysCache(fc_tup);

	table_close(pg_class_desc, RowExclusiveLock);
}

/*
 *		DeleteAttributeTuples
 *
 * 移除给定 relid 的 pg_attribute 行。
 *
 * 注意：这由关系删除和索引删除共享。
 * 不打算在其他地方使用。
 */
void DeleteAttributeTuples(Oid fc_relid)
{
	Relation	fc_attrel;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];
	HeapTuple	fc_atttup;

	/* 在 pg_attribute 关系上获取适当的锁 */
	fc_attrel = table_open(AttributeRelationId, RowExclusiveLock);

	/* 使用索引仅扫描目标关系的属性 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_attribute_attrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));

	fc_scan = systable_beginscan(fc_attrel, AttributeRelidNumIndexId, true,
							  NULL, 1, fc_key);

	/* 删除所有匹配的元组 */
	while ((fc_atttup = systable_getnext(fc_scan)) != NULL)
		CatalogTupleDelete(fc_attrel, &fc_atttup->t_self);

	/* 扫描后清理 */
	systable_endscan(fc_scan);
	table_close(fc_attrel, RowExclusiveLock);
}

/*
 *		DeleteSystemAttributeTuples
 *
 * 移除给定 relid 的系统列的 pg_attribute 行。
 *
 * 注意：这仅在将表转换为视图时使用。
 * 视图没有系统列，因此我们应该从 pg_attribute 中删除它们。
 */
void DeleteSystemAttributeTuples(Oid fc_relid)
{
	Relation	fc_attrel;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	HeapTuple	fc_atttup;

	/* 在 pg_attribute 关系上获取适当的锁 */
	fc_attrel = table_open(AttributeRelationId, RowExclusiveLock);

	/* 使用索引仅扫描目标关系的系统属性 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_attribute_attrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));
	ScanKeyInit(&fc_key[1],
				Anum_pg_attribute_attnum,
				BTLessEqualStrategyNumber, F_INT2LE,
				Int16GetDatum(0));

	fc_scan = systable_beginscan(fc_attrel, AttributeRelidNumIndexId, true,
							  NULL, 2, fc_key);

	/* 删除所有匹配的元组 */
	while ((fc_atttup = systable_getnext(fc_scan)) != NULL)
		CatalogTupleDelete(fc_attrel, &fc_atttup->t_self);

	/* 扫描后清理 */
	systable_endscan(fc_scan);
	table_close(fc_attrel, RowExclusiveLock);
}

/*
 *		RemoveAttributeById
 *
 * 这是 ALTER TABLE DROP COLUMN 的核心：实际上在 pg_attribute 中标记属性为已删除。
 * 我们还删除其 pg_statistic 条目。
 * （所有其他需要的东西，例如处理任何 pg_attrdef 条目，都由 dependency.c 处理。）
 */
void RemoveAttributeById(Oid fc_relid, AttrNumber fc_attnum)
{
	Relation	fc_rel;
	Relation	fc_attr_rel;
	HeapTuple	fc_tuple;
	Form_pg_attribute fc_attStruct;
	char		fc_newattname[NAMEDATALEN];

	/*
	 * 在目标表上获取独占锁，在事务结束之前我们不会释放它。
	 * （在直接删除此列的简单情况下，ATExecDropColumn 已经完成这一步... 
	 * 但是在从删除其他对象的级联情况时，我们可能没有任何锁。）
	 */
	fc_rel = relation_open(fc_relid, AccessExclusiveLock);

	fc_attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCacheCopy2(ATTNUM,
								ObjectIdGetDatum(fc_relid),
								Int16GetDatum(fc_attnum));
	if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
			 fc_attnum, fc_relid);
	fc_attStruct = (Form_pg_attribute) GETSTRUCT(fc_tuple);

	if (fc_attnum < 0)
	{
		/* 系统属性（可能是 OID）... 直接删除行 */

		CatalogTupleDelete(fc_attr_rel, &fc_tuple->t_self);
	}
	else
	{
		/* 删除用户属性要困难得多 */

		/* 将属性标记为已删除 */
		fc_attStruct->attisdropped = true;

		/*
		 * 将类型 OID 设置为无效。已删除属性的类型链接
		 * 不能被信赖（一旦属性被删除，类型也可能被删除）。
		 * 幸运的是，我们不需要类型行 --- 唯一真正必要的信息是类型的 typlen 和 typalign，
		 * 它们保存在属性的 attlen 和 attalign 中。我们在这里将 atttypid 设置为零，以捕捉错误期望它有效的代码。
		 */
		fc_attStruct->atttypid = InvalidOid;

		/* 移除列可能具有的任何 NOT NULL 约束 */
		fc_attStruct->attnotnull = false;

		/* 我们不想再保留其统计信息 */
		fc_attStruct->attstattarget = 0;

		/* 取消设置此项，以便没有人尝试查找生成表达式 */
		fc_attStruct->attgenerated = '\0';

		/*
		 * 将列名更改为不太可能冲突的名称
		 */
		snprintf(fc_newattname, sizeof(fc_newattname),
				 "........pg.dropped.%d........", fc_attnum);
		namestrcpy(&(fc_attStruct->attname), fc_newattname);

		/* 如果有，清除缺失的值 */
		if (fc_attStruct->atthasmissing)
		{
			Datum		fc_valuesAtt[Natts_pg_attribute];
			bool		fc_nullsAtt[Natts_pg_attribute];
			bool		fc_replacesAtt[Natts_pg_attribute];

			/* 更新元组 - 设置 atthasmissing 和 attmissingval */
			MemSet(fc_valuesAtt, 0, sizeof(fc_valuesAtt));
			MemSet(fc_nullsAtt, false, sizeof(fc_nullsAtt));
			MemSet(fc_replacesAtt, false, sizeof(fc_replacesAtt));

			fc_valuesAtt[Anum_pg_attribute_atthasmissing - 1] =
				BoolGetDatum(false);
			fc_replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
			fc_valuesAtt[Anum_pg_attribute_attmissingval - 1] = (Datum) 0;
			fc_nullsAtt[Anum_pg_attribute_attmissingval - 1] = true;
			fc_replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;

			fc_tuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_attr_rel),
									  fc_valuesAtt, fc_nullsAtt, fc_replacesAtt);
		}

		CatalogTupleUpdate(fc_attr_rel, &fc_tuple->t_self, fc_tuple);
	}

	/*
	 * 因为更新 pg_attribute 行会触发目标关系的 relcache 刷新，我们
	 * 不需要做任何其他事情来通知其他后端这个变化。
	 */

	table_close(fc_attr_rel, RowExclusiveLock);

	if (fc_attnum > 0)
		RemoveStatistics(fc_relid, fc_attnum);

	relation_close(fc_rel, NoLock);
}

/*
 * heap_drop_with_catalog - 从目录中移除指定关系
 *
 * 请注意，这个例程不负责删除通过依赖关系链接到 pg_class 
 * 条目的对象（例如，索引和约束）。那些将在控制到达这里之前
 * 通过 dependency.c 中的依赖跟踪逻辑删除。因此，通常情况下，
 * 这个例程不应该直接调用；而是通过 performDeletion() 来进行。
 */
void heap_drop_with_catalog(Oid fc_relid)
{
	Relation	fc_rel;
	HeapTuple	fc_tuple;
	Oid			fc_parentOid = InvalidOid,
				fc_defaultPartOid = InvalidOid;

	/*
	 * 要安全地删除分区，我们必须对其父关系获取排他锁，
	 * 因为其他后端可能正准备对父表执行查询。如果它依赖于
	 * 先前缓存的分区描述符，那么它可能试图将刚刚删除的关系
	 * 作为其分区访问。因此，我们必须对表获取足够强的表锁，
	 * 以防止所有查询在我们提交并发送共享缓存失效通知之前继续
	 * 进行，从而使它们更新其分区描述符。
	 */
	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	if (((Form_pg_class) GETSTRUCT(fc_tuple))->relispartition)
	{
		/*
		 * 如果分区正在分离，我们必须锁定父关系，
		 * 因为某些查询可能仍然有包含该分区的分区描述符。
		 */
		fc_parentOid = get_partition_parent(fc_relid, true);
		LockRelationOid(fc_parentOid, AccessExclusiveLock);

		/*
		 * 如果这不是默认分区，删除它将更改默认分区的分区约束，
		 * 所以我们必须锁定它。
		 */
		fc_defaultPartOid = get_default_partition_oid(fc_parentOid);
		if (OidIsValid(fc_defaultPartOid) && fc_relid != fc_defaultPartOid)
			LockRelationOid(fc_defaultPartOid, AccessExclusiveLock);
	}

	ReleaseSysCache(fc_tuple);

	/*
	 * 打开并锁定关系。
	 */
	fc_rel = relation_open(fc_relid, AccessExclusiveLock);

	/*
	 * 现在不再有其他人*触碰*这个关系了，但我们
	 * 可能仍在自己的会话中有打开的查询或游标，或待处理的触发事件。
	 */
	CheckTableNotInUse(fc_rel, "DROP TABLE");

	/*
	 * 这实际上删除了表中的所有行，可能在一个
	 * 可串行化事务中完成。在这种情况下，我们必须记录一个
	 * rw-conflict 到这个事务中，来自每个对表持有谓词锁的事务。
	 */
	CheckTableForSerializableConflictIn(fc_rel);

	/*
	 * 首先删除 pg_foreign_table 元组。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
	{
		Relation	fc_rel;
		HeapTuple	fc_tuple;

		fc_rel = table_open(ForeignTableRelationId, RowExclusiveLock);

		fc_tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(fc_relid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for foreign table %u", fc_relid);

		CatalogTupleDelete(fc_rel, &fc_tuple->t_self);

		ReleaseSysCache(fc_tuple);
		table_close(fc_rel, RowExclusiveLock);
	}

	/*
	 * 如果是分区表，删除 pg_partitioned_table 元组。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		RemovePartitionKeyByRelId(fc_relid);

	/*
	 * 如果要删除的关系就是默认分区本身，
	 * 则使其在 pg_partitioned_table 中的条目失效。
	 */
	if (fc_relid == fc_defaultPartOid)
		update_default_partition_oid(fc_parentOid, InvalidOid);

	/*
	 * 在提交时安排解除关系的物理文件连接。
	 */
	if (RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind))
		RelationDropStorage(fc_rel);

	/* 确保如果事务提交，则统计信息被删除 */
	pgstat_drop_relation(fc_rel);

	/*
	 * 关闭 relcache 条目，但*保持*对关系的 AccessExclusiveLock
	 * 直到事务提交。这确保没有其他人会尝试对注定
	 * 被删除的关系进行其他操作。
	 */
	relation_close(fc_rel, NoLock);

	/*
	 * 移除任何相关的关系同步状态。
	 */
	RemoveSubscriptionRel(InvalidOid, fc_relid);

	/*
	 * 忘记该关系的任何 ON COMMIT 操作
	 */
	remove_on_commit_action(fc_relid);

	/*
	 * 从 relcache 中刷新关系。我们希望在开始删除目录条目之前
	 * 执行此操作，以确保 在此过程中不会发生 relcache 条目重建。
	 * （这实际上不应该很重要，因为我们在这里不执行 
	 * CommandCounterIncrement，但让我们保持安全。）
	 */
	RelationForgetRelation(fc_relid);

	/*
	 * 移除继承信息
	 */
	fc_RelationRemoveInheritance(fc_relid);

	/*
	 * 删除统计信息
	 */
	RemoveStatistics(fc_relid, 0);

	/*
	 * 删除属性元组
	 */
	DeleteAttributeTuples(fc_relid);

	/*
	 * 删除关系元组
	 */
	DeleteRelationTuple(fc_relid);

	if (OidIsValid(fc_parentOid))
	{
		/*
		 * 如果这不是默认分区，默认分区的分区约束已经
		 * 更改为包含之前被删除的分区覆盖的键空间部分。
		 */
		if (OidIsValid(fc_defaultPartOid) && fc_relid != fc_defaultPartOid)
			CacheInvalidateRelcacheByRelid(fc_defaultPartOid);

		/*
		 * 使父关系的 relcache 失效，以便分区不再包含
		 * 在其分区描述符中。
		 */
		CacheInvalidateRelcacheByRelid(fc_parentOid);
		/* 保持锁定 */
	}
}


/*
 * RelationClearMissing
 *
 * 将所有属性的 atthasmissing 和 attmissingval 设置为
 * false/null，前提是它们目前被设置。 如果表被重写
 * （例如，通过 VACUUM FULL 或 CLUSTER），在这种情况下
 * 可以安全和有用地执行此操作，因为我们知道没有行
 * 留下的属性数量少于完整的补充。
 *
 * 调用者必须对关系拥有 AccessExclusive 锁。
 */
void RelationClearMissing(Relation fc_rel)
{
	Relation	fc_attr_rel;
	Oid			fc_relid = RelationGetRelid(fc_rel);
	int			fc_natts = RelationGetNumberOfAttributes(fc_rel);
	int			fc_attnum;
	Datum		fc_repl_val[Natts_pg_attribute];
	bool		fc_repl_null[Natts_pg_attribute];
	bool		fc_repl_repl[Natts_pg_attribute];
	Form_pg_attribute fc_attrtuple;
	HeapTuple	fc_tuple,
				fc_newtuple;

	memset(fc_repl_val, 0, sizeof(fc_repl_val));
	memset(fc_repl_null, false, sizeof(fc_repl_null));
	memset(fc_repl_repl, false, sizeof(fc_repl_repl));

	fc_repl_val[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(false);
	fc_repl_null[Anum_pg_attribute_attmissingval - 1] = true;

	fc_repl_repl[Anum_pg_attribute_atthasmissing - 1] = true;
	fc_repl_repl[Anum_pg_attribute_attmissingval - 1] = true;


	/* 获取对 pg_attribute 的锁定 */
	fc_attr_rel = table_open(AttributeRelationId, RowExclusiveLock);

	/* 处理每个非系统属性，包括任何被删除的列 */
	for (fc_attnum = 1; fc_attnum <= fc_natts; fc_attnum++)
	{
		fc_tuple = SearchSysCache2(ATTNUM,
								ObjectIdGetDatum(fc_relid),
								Int16GetDatum(fc_attnum));
		if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
				 fc_attnum, fc_relid);

		fc_attrtuple = (Form_pg_attribute) GETSTRUCT(fc_tuple);

		/* 忽略任何 atthasmissing 不为真者 */
		if (fc_attrtuple->atthasmissing)
		{
			fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_attr_rel),
										 fc_repl_val, fc_repl_null, fc_repl_repl);

			CatalogTupleUpdate(fc_attr_rel, &fc_newtuple->t_self, fc_newtuple);

			heap_freetuple(fc_newtuple);
		}

		ReleaseSysCache(fc_tuple);
	}

	/*
	 * 我们对 pg_attribute 行的更新将强制进行 relcache 重建，
	 * 因此这里没有其他事情要做。
	 */
	table_close(fc_attr_rel, RowExclusiveLock);
}

/*
 * SetAttrMissing
 *
 * 设置单个属性的缺失值。此方法仅应由
 * 二进制升级使用。对拥有该属性的关系进行排他访问锁定。
 */
void SetAttrMissing(Oid fc_relid, char *fc_attname, char *fc_value)
{
	Datum		fc_valuesAtt[Natts_pg_attribute];
	bool		fc_nullsAtt[Natts_pg_attribute];
	bool		fc_replacesAtt[Natts_pg_attribute];
	Datum		fc_missingval;
	Form_pg_attribute fc_attStruct;
	Relation	fc_attrrel,
				fc_tablerel;
	HeapTuple	fc_atttup,
				fc_newtup;

	/* 锁定属性所属的表 */
	fc_tablerel = table_open(fc_relid, AccessExclusiveLock);

	/* 除非是普通表，否则不执行任何操作 */
	if (fc_tablerel->rd_rel->relkind != RELKIND_RELATION)
	{
		table_close(fc_tablerel, AccessExclusiveLock);
		return;
	}

	/* 锁定属性行并获取数据 */
	fc_attrrel = table_open(AttributeRelationId, RowExclusiveLock);
	fc_atttup = SearchSysCacheAttName(fc_relid, fc_attname);
	if (!HeapTupleIsValid(fc_atttup))
		elog(ERROR, "cache lookup failed for attribute %s of relation %u",
			 fc_attname, fc_relid);
	fc_attStruct = (Form_pg_attribute) GETSTRUCT(fc_atttup);

	/* 从值字符串中获取数组值 */
	fc_missingval = OidFunctionCall3(F_ARRAY_IN,
								  CStringGetDatum(fc_value),
								  ObjectIdGetDatum(fc_attStruct->atttypid),
								  Int32GetDatum(fc_attStruct->atttypmod));

	/* 更新元组 - 设置 atthasmissing 和 attmissingval */
	MemSet(fc_valuesAtt, 0, sizeof(fc_valuesAtt));
	MemSet(fc_nullsAtt, false, sizeof(fc_nullsAtt));
	MemSet(fc_replacesAtt, false, sizeof(fc_replacesAtt));

	fc_valuesAtt[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(true);
	fc_replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
	fc_valuesAtt[Anum_pg_attribute_attmissingval - 1] = fc_missingval;
	fc_replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;

	fc_newtup = heap_modify_tuple(fc_atttup, RelationGetDescr(fc_attrrel),
							   fc_valuesAtt, fc_nullsAtt, fc_replacesAtt);
	CatalogTupleUpdate(fc_attrrel, &fc_newtup->t_self, fc_newtup);

	/* 清理 */
	ReleaseSysCache(fc_atttup);
	table_close(fc_attrrel, RowExclusiveLock);
	table_close(fc_tablerel, AccessExclusiveLock);
}

/*
 * 存储给定关系的检查约束表达式。
 *
 * 调用者负责更新关系的 pg_class 条目中的约束计数。
 *
 * 返回新约束的 OID。
 */
static Oid fc_StoreRelCheck(Relation fc_rel, const char *fc_ccname, Node *fc_expr,
			  bool fc_is_validated, bool fc_is_local, int fc_inhcount,
			  bool fc_is_no_inherit, bool fc_is_internal)
{
	char	   *fc_ccbin;
	List	   *fc_varList;
	int			fc_keycount;
	int16	   *fc_attNos;
	Oid			fc_constrOid;

	/*
	 * 将表达式展平为字符串形式以进行存储。
	 */
	fc_ccbin = nodeToString(fc_expr);

	/*
	 * 查找在 expr 中使用的关系的列
	 *
	 * 注意：这里的 pull_var_clause 是可以的，因为我们不允许在检查约束中使用子选择；它将无法检查子选择的内容。
	 */
	fc_varList = pull_var_clause(fc_expr, 0);
	fc_keycount = list_length(fc_varList);

	if (fc_keycount > 0)
	{
		ListCell   *fc_vl;
		int			fc_i = 0;

		fc_attNos = (int16 *) palloc(fc_keycount * sizeof(int16));
		foreach(fc_vl, fc_varList)
		{
			Var		   *fc_var = (Var *) lfirst(fc_vl);
			int			fc_j;

			for (fc_j = 0; fc_j < fc_i; fc_j++)
				if (fc_attNos[fc_j] == fc_var->varattno)
					break;
			if (fc_j == fc_i)
				fc_attNos[fc_i++] = fc_var->varattno;
		}
		fc_keycount = fc_i;
	}
	else
		fc_attNos = NULL;

	/*
	 * 分区表本身不包含任何行，因此 NO INHERIT
	 * 约束毫无意义。
	 */
	if (fc_is_no_inherit &&
		fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
				 errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"",
						RelationGetRelationName(fc_rel))));

	/*
	 * 创建检查约束
	 */
	fc_constrOid =
		CreateConstraintEntry(fc_ccname,	/* 约束名称 */
							  RelationGetNamespace(fc_rel),	/* 名称空间 */
							  CONSTRAINT_CHECK, /* 约束类型 */
							  false,	/* 是否可延迟 */
							  false,	/* 是否延迟 */
							  fc_is_validated,
							  InvalidOid,	/* 没有父约束 */
							  RelationGetRelid(fc_rel),	/* 关系 */
							  fc_attNos,	/* 约束中的属性 */
							  fc_keycount, /* 约束中的关键属性数量 */
							  fc_keycount, /* 约束中的总属性数量 */
							  InvalidOid,	/* 不是域约束 */
							  InvalidOid,	/* 没有关联索引 */
							  InvalidOid,	/* 外键字段 */
							  NULL,
							  NULL,
							  NULL,
							  NULL,
							  0,
							  ' ',
							  ' ',
							  NULL,
							  0,
							  ' ',
							  NULL, /* 不是排除约束 */
							  fc_expr, /* 检查约束的树形结构 */
							  fc_ccbin,	/* 检查约束的二进制形式 */
							  fc_is_local, /* conislocal */
							  fc_inhcount, /* coninhcount */
							  fc_is_no_inherit,	/* connoinherit */
							  fc_is_internal); /* 是否为内部构建的？ */

	pfree(fc_ccbin);

	return fc_constrOid;
}

/*
 * 存储默认值和约束（作为 CookedConstraint 的列表传递）。
 *
 * 每个 CookedConstraint 结构会被修改以存储新的目录元组 OID。
 *
 * 注意：只有预先处理过的表达式会以这种方式传递，也就是说，来自现有关系的表达式。 新解析的
 * 表达式可以稍后通过直接调用 StoreAttrDefault
 * 和 StoreRelCheck（见 AddRelationNewConstraints()）添加。
 */
static void fc_StoreConstraints(Relation fc_rel, List *fc_cooked_constraints, bool fc_is_internal)
{
	int			fc_numchecks = 0;
	ListCell   *fc_lc;

	if (fc_cooked_constraints == NIL)
		return;					/* 无需操作 */

	/*
	 * 约束表达式的去解析将失败，除非刚创建的
	 * pg_attribute 元组对该关系可见。因此，增加命令计数器。小心：这将导致
	 * relcache 条目的重建。
	 */
	CommandCounterIncrement();

	foreach(fc_lc, fc_cooked_constraints)
	{
		CookedConstraint *fc_con = (CookedConstraint *) lfirst(fc_lc);

		switch (fc_con->contype)
		{
			case CONSTR_DEFAULT:
				fc_con->conoid = StoreAttrDefault(fc_rel, fc_con->attnum, fc_con->expr,
											   fc_is_internal, false);
				break;
			case CONSTR_CHECK:
				fc_con->conoid =
					fc_StoreRelCheck(fc_rel, fc_con->name, fc_con->expr,
								  !fc_con->skip_validation, fc_con->is_local,
								  fc_con->inhcount, fc_con->is_no_inherit,
								  fc_is_internal);
				fc_numchecks++;
				break;
			default:
				elog(ERROR, "unrecognized constraint type: %d",
					 (int) fc_con->contype);
		}
	}

	if (fc_numchecks > 0)
		fc_SetRelationNumChecks(fc_rel, fc_numchecks);
}

/*
 * AddRelationNewConstraints
 *
 * 向现有关系添加新的列默认表达式和/或约束检查表达式。 这被定义为
 * 为了在 DefineRelation 中提高效率做这两者，但当然您可以通过传递
 * 空列表只执行其中之一。
 *
 * rel：要修改的关系
 * newColDefaults：RawColumnDefault 结构的列表
 * newConstraints：Constraint 节点的列表
 * allow_merge：如果检查约束可以与现有的合并，则为 true
 * is_local：如果定义是本地的，则为 true；如果是继承的，则为 false
 * is_internal：如果是某个内部过程的结果，而不是用户请求，则为 true
 *
 * newColDefaults 中的所有条目将被处理。只有当 newConstraints
 * 中的条目是 CONSTR_CHECK 类型时，才会处理。
 *
 * 返回一个 CookedConstraint 节点的列表，显示添加到关系的
 * 默认值和约束表达式的熟成形式。
 *
 * 注意：调用者应该以某种自冲突的锁模式打开 rel，
 * 并应在事务结束前保持该锁；对于正常情况，这将是
 * AccessExclusiveLock，但如果调用者知道约束已通过其他方式强制执行，则可以使用
 * ShareUpdateExclusiveLock。此外，我们假设调用者在必要时已经
 * 执行了 CommandCounterIncrement，以使关系的目录元组可见。
 */
List * AddRelationNewConstraints(Relation fc_rel,
						  List *fc_newColDefaults,
						  List *fc_newConstraints,
						  bool fc_allow_merge,
						  bool fc_is_local,
						  bool fc_is_internal,
						  const char *fc_queryString)
{
	List	   *fc_cookedConstraints = NIL;
	TupleDesc	fc_tupleDesc;
	TupleConstr *fc_oldconstr;
	int			fc_numoldchecks;
	ParseState *fc_pstate;
	ParseNamespaceItem *fc_nsitem;
	int			fc_numchecks;
	List	   *fc_checknames;
	ListCell   *fc_cell;
	Node	   *fc_expr;
	CookedConstraint *fc_cooked;

	/*
	 * 获取有关现有约束的信息。
	 */
	fc_tupleDesc = RelationGetDescr(fc_rel);
	fc_oldconstr = fc_tupleDesc->constr;
	if (fc_oldconstr)
		fc_numoldchecks = fc_oldconstr->num_check;
	else
		fc_numoldchecks = 0;

	/*
	 * 创建一个虚拟的 ParseState，并将目标关系插入为其唯一的
	 * rangetable 项。我们需要一个 ParseState 用于 transformExpr。
	 */
	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;
	fc_nsitem = addRangeTableEntryForRelation(fc_pstate,
										   fc_rel,
										   AccessShareLock,
										   NULL,
										   false,
										   true);
	addNSItemToQuery(fc_pstate, fc_nsitem, true, true, true);

	/*
	 * 处理列默认表达式。
	 */
	foreach(fc_cell, fc_newColDefaults)
	{
		RawColumnDefault *fc_colDef = (RawColumnDefault *) lfirst(fc_cell);
		Form_pg_attribute fc_atp = TupleDescAttr(fc_rel->rd_att, fc_colDef->attnum - 1);
		Oid			fc_defOid;

		fc_expr = cookDefault(fc_pstate, fc_colDef->raw_default,
						   fc_atp->atttypid, fc_atp->atttypmod,
						   NameStr(fc_atp->attname),
						   fc_atp->attgenerated);

		/*
		 * 如果表达式只是 NULL 常量，我们不费心显式创建
		 * pg_attrdef 条目，因为默认行为是
		 * 等效的。这适用于列默认值，但不适用于
		 * 生成功能。
		 *
		 * 注意：这个测试的一个非显而易见的属性是：如果列是域类型，则我们得到的不是
		 * 一个裸的 null Const，而是一个 CoerceToDomain 表达式，因此我们不会丢弃默认值。这
		 * 是至关重要的，因为需要保留列默认值以覆盖域可能具有的任何默认值。
		 */
		if (fc_expr == NULL ||
			(!fc_colDef->generated &&
			 IsA(fc_expr, Const) &&
			 castNode(Const, fc_expr)->constisnull))
			continue;

		/* 如果 DEFAULT 是可变的，我们无法使用缺失值 */
		if (fc_colDef->missingMode &&
			contain_volatile_functions_after_planning((Expr *) fc_expr))
			fc_colDef->missingMode = false;

		fc_defOid = StoreAttrDefault(fc_rel, fc_colDef->attnum, fc_expr, fc_is_internal,
								  fc_colDef->missingMode);

		fc_cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
		fc_cooked->contype = CONSTR_DEFAULT;
		fc_cooked->conoid = fc_defOid;
		fc_cooked->name = NULL;
		fc_cooked->attnum = fc_colDef->attnum;
		fc_cooked->expr = fc_expr;
		fc_cooked->skip_validation = false;
		fc_cooked->is_local = fc_is_local;
		fc_cooked->inhcount = fc_is_local ? 0 : 1;
		fc_cooked->is_no_inherit = false;
		fc_cookedConstraints = lappend(fc_cookedConstraints, fc_cooked);
	}

	/*
	 * 处理约束表达式。
	 */
	fc_numchecks = fc_numoldchecks;
	fc_checknames = NIL;
	foreach(fc_cell, fc_newConstraints)
	{
		Constraint *fc_cdef = (Constraint *) lfirst(fc_cell);
		char	   *fc_ccname;
		Oid			fc_constrOid;

		if (fc_cdef->contype != CONSTR_CHECK)
			continue;

		if (fc_cdef->raw_expr != NULL)
		{
			Assert(fc_cdef->cooked_expr == NULL);

			
/*
			 * 将原始解析树转换为可执行的表达式，并验证
			 * 其作为 CHECK 约束的有效性。
			 */
			fc_expr = fc_cookConstraint(fc_pstate, fc_cdef->raw_expr,
								  RelationGetRelationName(fc_rel));
		}
		else
		{
			Assert(fc_cdef->cooked_expr != NULL);

			/*
			 * 在这里，我们假设解析器只会传递有效的 CHECK
			 * 表达式，因此我们不进行特殊检查。
			 */
			fc_expr = stringToNode(fc_cdef->cooked_expr);
		}

		/*
		 * 检查名称的唯一性，或者在未给出名称时生成一个名称。
		 */
		if (fc_cdef->conname != NULL)
		{
			ListCell   *fc_cell2;

			fc_ccname = fc_cdef->conname;
			/* 检查其他新约束 */
			/* 由于我们在循环中不执行 CommandCounterIncrement，所以需要这个 */
			foreach(fc_cell2, fc_checknames)
			{
				if (strcmp((char *) lfirst(fc_cell2), fc_ccname) == 0)
					ereport(ERROR,
							(errcode(ERRCODE_DUPLICATE_OBJECT),
							 errmsg("check constraint \"%s\" already exists",
									fc_ccname)));
			}

			/* 保存名称以备将来检查 */
			fc_checknames = lappend(fc_checknames, fc_ccname);

			/* 
			 * 检查与现有约束的冲突。如果我们允许与现有约束合并，这里就不需要更多操作了。
			 * （我们从结果中省略了重复的约束，这是 ATAddCheckConstraint 想要的。）
			 */
			if (fc_MergeWithExistingConstraint(fc_rel, fc_ccname, fc_expr,
											fc_allow_merge, fc_is_local,
											fc_cdef->initially_valid,
											fc_cdef->is_no_inherit))
				continue;
		}
		else
		{
			/* 
			 * 生成名称时，我们想为列约束创建 "tab_col_check"，为表约束创建 "tab_check"。
			 * 我们不再拥有任何有关约束短语的句法位置的信息，因此我们通过查看表达式是否引用多个列来进行近似。
			 * （如果用户遵循规则，结果是相同的……）
			 *
			 * 注意：pull_var_clause() 不会深入子链接，但我们在上面已消除这些；而且无论如何这只需要是一个近似答案。
			 */
			List	   *fc_vars;
			char	   *fc_colname;

			fc_vars = pull_var_clause(fc_expr, 0);

			/* 消除重复项 */
			fc_vars = list_union(NIL, fc_vars);

			if (list_length(fc_vars) == 1)
				fc_colname = get_attname(RelationGetRelid(fc_rel),
									  ((Var *) linitial(fc_vars))->varattno,
									  true);
			else
				fc_colname = NULL;

			fc_ccname = ChooseConstraintName(RelationGetRelationName(fc_rel),
										  fc_colname,
										  "check",
										  RelationGetNamespace(fc_rel),
										  fc_checknames);

			/* 保存名称以备将来检查 */
			fc_checknames = lappend(fc_checknames, fc_ccname);
		}

		/* 
		 * 好的，存储它。
		 */
		fc_constrOid =
			fc_StoreRelCheck(fc_rel, fc_ccname, fc_expr, fc_cdef->initially_valid, fc_is_local,
						  fc_is_local ? 0 : 1, fc_cdef->is_no_inherit, fc_is_internal);

		fc_numchecks++;

		fc_cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
		fc_cooked->contype = CONSTR_CHECK;
		fc_cooked->conoid = fc_constrOid;
		fc_cooked->name = fc_ccname;
		fc_cooked->attnum = 0;
		fc_cooked->expr = fc_expr;
		fc_cooked->skip_validation = fc_cdef->skip_validation;
		fc_cooked->is_local = fc_is_local;
		fc_cooked->inhcount = fc_is_local ? 0 : 1;
		fc_cooked->is_no_inherit = fc_cdef->is_no_inherit;
		fc_cookedConstraints = lappend(fc_cookedConstraints, fc_cooked);
	}

	/*
	 * 更新关系 pg_class 元组中的约束计数。即使没有更改，我们也会这样做，以确保为 pg_class 元组发送
	 * SI 更新消息，这将强制其他后端重建它们的 relcache 入口（这在我们添加了默认值但没有添加约束时至关重要）。
	 */
	fc_SetRelationNumChecks(fc_rel, fc_numchecks);

	return fc_cookedConstraints;
}

/*
 * 检查是否存在与提议的新检查约束冲突的现有检查约束，并根据需要调整其 conislocal/coninhcount 设置或抛出错误。
 *
 * 如果合并（约束是重复的），则返回 true；如果它有到目前为止唯一的名称，则返回 false；如果发生冲突则抛出错误。
 *
 * XXX 如果您更改此代码，请同时查看 MergeConstraintsIntoExisting。
 */
static bool fc_MergeWithExistingConstraint(Relation fc_rel, const char *fc_ccname, Node *fc_expr,
							bool fc_allow_merge, bool fc_is_local,
							bool fc_is_initially_valid,
							bool fc_is_no_inherit)
{
	bool		fc_found;
	Relation	fc_conDesc;
	SysScanDesc fc_conscan;
	ScanKeyData fc_skey[3];
	HeapTuple	fc_tup;

	/* 搜索具有相同名称和关系的 pg_constraint 条目 */
	fc_conDesc = table_open(ConstraintRelationId, RowExclusiveLock);

	fc_found = false;

	ScanKeyInit(&fc_skey[0],
				Anum_pg_constraint_conrelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	ScanKeyInit(&fc_skey[1],
				Anum_pg_constraint_contypid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(InvalidOid));
	ScanKeyInit(&fc_skey[2],
				Anum_pg_constraint_conname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(fc_ccname));

	fc_conscan = systable_beginscan(fc_conDesc, ConstraintRelidTypidNameIndexId, true,
								 NULL, 3, fc_skey);

	/* 最多只能有一行匹配 */
	if (HeapTupleIsValid(fc_tup = systable_getnext(fc_conscan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tup);

		/* 找到它。如果不是完全相同的检查约束则冲突 */
		if (fc_con->contype == CONSTRAINT_CHECK)
		{
			Datum		fc_val;
			bool		fc_isnull;

			fc_val = fastgetattr(fc_tup,
							  Anum_pg_constraint_conbin,
							  fc_conDesc->rd_att, &fc_isnull);
			if (fc_isnull)
				elog(ERROR, "null conbin for rel %s",
					 RelationGetRelationName(fc_rel));
			if (equal(fc_expr, stringToNode(TextDatumGetCString(fc_val))))
				fc_found = true;
		}

		/* 
		 * 如果现有约束仅是继承的（没有本地定义），则将本地约束的添加解释为合法合并。
		 * 这允许在父表和子表上以任意顺序提供 ALTER ADD CONSTRAINT，最终状态相同。
		 * 但是，如果关系是一个分区，则所有继承的约束始终都是非本地的，包括那些被合并的约束。
		 */
		if (fc_is_local && !fc_con->conislocal && !fc_rel->rd_rel->relispartition)
			fc_allow_merge = true;

		if (!fc_found || !fc_allow_merge)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("constraint \"%s\" for relation \"%s\" already exists",
							fc_ccname, RelationGetRelationName(fc_rel))));

		/* 如果子约束是“不可继承”，则无法合并 */
		if (fc_con->connoinherit)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"",
							fc_ccname, RelationGetRelationName(fc_rel))));

		/* 
		 * 不能将现有的继承约束更改为“无继承”状态。
		 * 这是因为继承的约束应该能够传播到下层子对象。
		 */
		if (fc_con->coninhcount > 0 && fc_is_no_inherit)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("constraint \"%s\" conflicts with inherited constraint on relation \"%s\"",
							fc_ccname, RelationGetRelationName(fc_rel))));

		/* 
		 * 如果子约束是“无效的”，那么无法与有效的父约束合并。
		 */
		if (fc_is_initially_valid && !fc_con->convalidated)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("constraint \"%s\" conflicts with NOT VALID constraint on relation \"%s\"",
							fc_ccname, RelationGetRelationName(fc_rel))));

		/* 可以更新元组 */
		ereport(NOTICE,
				(errmsg("merging constraint \"%s\" with inherited definition",
						fc_ccname)));

		fc_tup = heap_copytuple(fc_tup);
		fc_con = (Form_pg_constraint) GETSTRUCT(fc_tup);

		/* 
		 * 在分区的情况下，继承的约束必须仅被继承一次，因为它不能有多个父项，并且它从不被视为本地。
		 */
		if (fc_rel->rd_rel->relispartition)
		{
			fc_con->coninhcount = 1;
			fc_con->conislocal = false;
		}
		else
		{
			if (fc_is_local)
				fc_con->conislocal = true;
			else
				fc_con->coninhcount++;
		}

		if (fc_is_no_inherit)
		{
			Assert(fc_is_local);
			fc_con->connoinherit = true;
		}

		CatalogTupleUpdate(fc_conDesc, &fc_tup->t_self, fc_tup);
	}

	systable_endscan(fc_conscan);
	table_close(fc_conDesc, RowExclusiveLock);

	return fc_found;
}

/* 
 * 更新关系 pg_class 元组中的约束计数。
 *
 * 调用者最好持有该关系的独占锁。
 *
 * 一个重要的副作用是，将为 pg_class 元组发送 SI 更新消息，这将强制其他后端重建其
 * relcache 条目。此后端也将在下一个 CommandCounterIncrement 时重建自己的 relcache 条目。
 */
static void fc_SetRelationNumChecks(Relation fc_rel, int fc_numchecks)
{
	Relation	fc_relrel;
	HeapTuple	fc_reltup;
	Form_pg_class fc_relStruct;

	fc_relrel = table_open(RelationRelationId, RowExclusiveLock);
	fc_reltup = SearchSysCacheCopy1(RELOID,
								 ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	if (!HeapTupleIsValid(fc_reltup))
		elog(ERROR, "cache lookup failed for relation %u",
			 RelationGetRelid(fc_rel));
	fc_relStruct = (Form_pg_class) GETSTRUCT(fc_reltup);

	if (fc_relStruct->relchecks != fc_numchecks)
	{
		fc_relStruct->relchecks = fc_numchecks;

		CatalogTupleUpdate(fc_relrel, &fc_reltup->t_self, fc_reltup);
	}
	else
	{
		/* 跳过磁盘更新，但仍强制 relcache 无效 */
		CacheInvalidateRelcache(fc_rel);
	}

	heap_freetuple(fc_reltup);
	table_close(fc_relrel, RowExclusiveLock);
}

/* 
 * 检查对生成列的引用
 */
static bool fc_check_nested_generated_walker(Node *fc_node, void *fc_context)
{
	ParseState *fc_pstate = fc_context;

	if (fc_node == NULL)
		return false;
	else if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;
		Oid			fc_relid;
		AttrNumber	fc_attnum;

		fc_relid = rt_fetch(fc_var->varno, fc_pstate->p_rtable)->relid;
		if (!OidIsValid(fc_relid))
			return false;		/* XXX 我们应该引发错误吗？ */

		fc_attnum = fc_var->varattno;

		if (fc_attnum > 0 && get_attgenerated(fc_relid, fc_attnum))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cannot use generated column \"%s\" in column generation expression",
							get_attname(fc_relid, fc_attnum, false)),
					 errdetail("A generated column cannot reference another generated column."),
					 parser_errposition(fc_pstate, fc_var->location)));
		/* 整行 Var 必然是自引用的，因此禁止它 */
		if (fc_attnum == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cannot use whole-row variable in column generation expression"),
					 errdetail("This would cause the generated column to depend on its own value."),
					 parser_errposition(fc_pstate, fc_var->location)));
		/* 系统列在解析器中已被检查 */

		return false;
	}
	else
		return expression_tree_walker(fc_node, fc_check_nested_generated_walker,
									  (void *) fc_context);
}

static void fc_check_nested_generated(ParseState *fc_pstate, Node *fc_node)
{
	fc_check_nested_generated_walker(fc_node, fc_pstate);
}

/* 
 * 获取原始默认值并将其转换为准备存储的熟悉格式。
 *
 * 解析状态应设置为识别表达式中可能出现的任何变量。
 * （即使我们计划拒绝变量，提供正确的错误信息比“未知变量”友好得多。）
 *
 * 如果 atttypid 不是 InvalidOid，则将表达式强制转换为指定类型（和 typmod atttypmod）。
 * attname 只在这种情况下需要：如果有错误消息，则使用它。
 */
Node * cookDefault(ParseState *fc_pstate,
			Node *fc_raw_default,
			Oid fc_atttypid,
			int32 fc_atttypmod,
			const char *fc_attname,
			char fc_attgenerated)
{
	Node	   *fc_expr;

	Assert(fc_raw_default != NULL);

	/* 
	 * 将原始解析树转换为可执行表达式。
	 */
	fc_expr = transformExpr(fc_pstate, fc_raw_default, fc_attgenerated ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT);

	if (fc_attgenerated)
	{
		/* 不允许引用其他生成列 */
		fc_check_nested_generated(fc_pstate, fc_expr);

		/* 不允许可变函数 */
		if (contain_mutable_functions_after_planning((Expr *) fc_expr))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("generation expression is not immutable")));
	}
	else
	{
		/* 
		 * 对于默认表达式，transformExpr() 应该已拒绝列引用。
		 */
		Assert(!contain_var_clause(fc_expr));
	}

	/* 
	 * 如果给定，将表达式强制转换为正确的类型和 typmod。这个
	 * 应该与解析器处理非默认表达式的方式匹配——
	 * 请参见 transformAssignedExpr()。
	 */
	if (OidIsValid(fc_atttypid))
	{
		Oid			fc_type_id = exprType(fc_expr);

		fc_expr = coerce_to_target_type(fc_pstate, fc_expr, fc_type_id,
									 fc_atttypid, fc_atttypmod,
									 COERCION_ASSIGNMENT,
									 COERCE_IMPLICIT_CAST,
									 -1);
		if (fc_expr == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("column \"%s\" is of type %s"
							" but default expression is of type %s",
							fc_attname,
							format_type_be(fc_atttypid),
							format_type_be(fc_type_id)),
					 errhint("You will need to rewrite or cast the expression.")));
	}

	/* 
	 * 最后，处理已完成表达式中的排序规则。
	 */
	assign_expr_collations(fc_pstate, fc_expr);

	return fc_expr;
}

/* 
 * 获取原始 CHECK 约束表达式并将其转换为准备存储的熟悉格式。
 *
 * 解析状态必须设置为识别表达式中可能出现的任何变量。
 */
static Node * fc_cookConstraint(ParseState *fc_pstate,
			   Node *fc_raw_constraint,
			   char *fc_relname)
{
	Node	   *fc_expr;

	/* 
	 * 将原始解析树转换为可执行表达式。
	 */
	fc_expr = transformExpr(fc_pstate, fc_raw_constraint, EXPR_KIND_CHECK_CONSTRAINT);

	/*
	 * 确保它返回布尔结果。
	 */
	fc_expr = coerce_to_boolean(fc_pstate, fc_expr, "CHECK");

	/* 
	 * 处理排序规则。
	 */  
	assign_expr_collations(fc_pstate, fc_expr);

	/*
	 * 确保没有外部关系被引用（这可能是死代码，因为 add_missing_from 已经被淘汰）。
	 */
	if (list_length(fc_pstate->p_rtable) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
				 errmsg("only table \"%s\" can be referenced in check constraint",
						fc_relname)));

	return fc_expr;
}

/*
 * CopyStatistics --- 从一个关系复制 pg_statistic 中的条目到另一个
 */
void CopyStatistics(Oid fc_fromrelid, Oid fc_torelid)
{
	HeapTuple	fc_tup;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[1];
	Relation	fc_statrel;

	fc_statrel = table_open(StatisticRelationId, RowExclusiveLock);

	/* 现在搜索 stat 记录 */
	ScanKeyInit(&fc_key[0],
				Anum_pg_statistic_starelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_fromrelid));

	fc_scan = systable_beginscan(fc_statrel, StatisticRelidAttnumInhIndexId,
							  true, NULL, 1, fc_key);

	while (HeapTupleIsValid((fc_tup = systable_getnext(fc_scan))))
	{
		Form_pg_statistic fc_statform;

		/* 制作一个可修改的副本 */
		fc_tup = heap_copytuple(fc_tup);
		fc_statform = (Form_pg_statistic) GETSTRUCT(fc_tup);

		/* 更新元组的副本并插入它 */
		fc_statform->starelid = fc_torelid;
		CatalogTupleInsert(fc_statrel, fc_tup);

		heap_freetuple(fc_tup);
	}

	systable_endscan(fc_scan);

	table_close(fc_statrel, RowExclusiveLock);
}

/*
 * RemoveStatistics --- 删除 pg_statistic 中某个关系或列的条目
 *
 * 如果 attnum 为零，则删除该关系的所有条目；否则仅删除该列的一个或多个条目。
 */
void RemoveStatistics(Oid fc_relid, AttrNumber fc_attnum)
{
	Relation	fc_pgstatistic;
	SysScanDesc fc_scan;
	ScanKeyData fc_key[2];
	int			fc_nkeys;
	HeapTuple	fc_tuple;

	fc_pgstatistic = table_open(StatisticRelationId, RowExclusiveLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_statistic_starelid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_relid));

	if (fc_attnum == 0)
		fc_nkeys = 1;
	else
	{
		ScanKeyInit(&fc_key[1],
					Anum_pg_statistic_staattnum,
					BTEqualStrategyNumber, F_INT2EQ,
					Int16GetDatum(fc_attnum));
		fc_nkeys = 2;
	}

	fc_scan = systable_beginscan(fc_pgstatistic, StatisticRelidAttnumInhIndexId, true,
							  NULL, fc_nkeys, fc_key);

	/* 即使 attnum != 0，我们也必须循环，以防遗传统计 */
	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_scan)))
		CatalogTupleDelete(fc_pgstatistic, &fc_tuple->t_self);

	systable_endscan(fc_scan);

	table_close(fc_pgstatistic, RowExclusiveLock);
}


/*
 * RelationTruncateIndexes - 将与堆关系关联的所有索引截断为零元组。
 *
 * 该例程将截断然后重构指定关系上的索引。调用者必须对关系保持独占锁定。
 */
static void fc_RelationTruncateIndexes(Relation fc_heapRelation)
{
	ListCell   *fc_indlist;

	/* 请 relcache 生成关系的索引列表 */
	foreach(fc_indlist, RelationGetIndexList(fc_heapRelation))
	{
		Oid			fc_indexId = lfirst_oid(fc_indlist);
		Relation	fc_currentIndex;
		IndexInfo  *fc_indexInfo;

		/* 打开索引关系；使用独占锁，以确保安全 */
		fc_currentIndex = index_open(fc_indexId, AccessExclusiveLock);

		/*
		 * 获取索引构建所需的信息。由于我们知道没有实际需要索引的元组，
		 * 我们可以使用一个虚拟的 IndexInfo。这构建起来稍微便宜一些，
		 * 但主要是为了避免在索引表达式或谓词中运行用户定义的代码。
		 * 我们可能在 ON COMMIT 处理期间被调用，而我们不希望在那时运行任何此类代码。
		 */
		fc_indexInfo = BuildDummyIndexInfo(fc_currentIndex);

		/*
		 * 现在截断实际文件（并丢弃缓冲区）。
		 */
		RelationTruncate(fc_currentIndex, 0);

		/* 初始化索引并重建 */
		/* 注意：我们不需要重新建立主键设置 */
		index_build(fc_heapRelation, fc_currentIndex, fc_indexInfo, true, false);

		/* 我们完成了这个索引 */
		index_close(fc_currentIndex, NoLock);
	}
}

/*
 *	 heap_truncate
 *
 *	 该例程删除所有指定关系中的所有数据。
 *
 * 这不是事务安全的！在 commands/tablecmds.c 中有另一个事务安全的实现。
 * 我们现在仅在临时表的 ON COMMIT 截断中使用它，这样是没问题的。
 */
void heap_truncate(List *fc_relids)
{
	List	   *fc_relations = NIL;
	ListCell   *fc_cell;

	/* 打开关系以进行处理，并在每个上获取独占访问权限 */
	foreach(fc_cell, fc_relids)
	{
		Oid			fc_rid = lfirst_oid(fc_cell);
		Relation	fc_rel;

		fc_rel = table_open(fc_rid, AccessExclusiveLock);
		fc_relations = lappend(fc_relations, fc_rel);
	}

	/* 不允许在被外键引用的表上进行截断 */
	heap_truncate_check_FKs(fc_relations, true);

	/* 可以这样做 */
	foreach(fc_cell, fc_relations)
	{
		Relation	fc_rel = lfirst(fc_cell);

		/* 截断关系 */
		heap_truncate_one_rel(fc_rel);

		/* 关闭关系，但在提交之前保持独占锁定 */
		table_close(fc_rel, NoLock);
	}
}

/*
 *	 heap_truncate_one_rel
 *
 *	 该例程删除指定关系中的所有数据。
 *
 * 这不是事务安全的，因为截断是立即执行的，不能在之后回滚。
 * 调用者负责检查权限等，并必须获得 AccessExclusiveLock。
 */
void heap_truncate_one_rel(Relation fc_rel)
{
	Oid			fc_toastrelid;

	/*
	 * 截断关系。分区表没有存储，所以这里对它们没有任何操作。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		return;

	/* 截断底层关系 */
	table_relation_nontransactional_truncate(fc_rel);

	/* 如果关系有索引，也截断索引 */
	fc_RelationTruncateIndexes(fc_rel);

	/* 如果有 toast 表，也截断它 */
	fc_toastrelid = fc_rel->rd_rel->reltoastrelid;
	if (OidIsValid(fc_toastrelid))
	{
		Relation	fc_toastrel = table_open(fc_toastrelid, AccessExclusiveLock);

		table_relation_nontransactional_truncate(fc_toastrel);
		fc_RelationTruncateIndexes(fc_toastrel);
		/* 保持锁定... */
		table_close(fc_toastrel, NoLock);
	}
}

/*
 * heap_truncate_check_FKs
 *		检查引用要截断的关系列表的外键，如果有则引发错误
 *
 * 我们不允许此类外键（自引用的外键除外），因为 TRUNCATE 的整个目的就是不扫描要丢弃的单独行。
 *
 * 这被拆分出来以便可以共享给两种截断实现。
 * 调用者应已在关系上保持适当的锁定。
 *
 * tempTables 仅用于选择适当的错误消息。
 */
void heap_truncate_check_FKs(List *fc_relations, bool fc_tempTables)
{
	List	   *fc_oids = NIL;
	List	   *fc_dependents;
	ListCell   *fc_cell;

	/*
	 * 构建一个有趣关系的 OID 列表。
	 *
	 * 如果关系没有触发器，则它既不能有外键也不能被其他表的外键引用，因此我们可以忽略它。
	 * 对于分区表，外键没有触发器，因此我们必须包含它们。
	 */
	foreach(fc_cell, fc_relations)
	{
		Relation	fc_rel = lfirst(fc_cell);

		if (fc_rel->rd_rel->relhastriggers ||
			fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
			fc_oids = lappend_oid(fc_oids, RelationGetRelid(fc_rel));
	}

	/*
	 * 快捷路径：如果没有关系有触发器，则也没有外键。
	 */
	if (fc_oids == NIL)
		return;

	/*
	 * 否则，必须扫描 pg_constraint。我们对所有关系进行一次遍历；如果没有找到任何内容，那么一切都好。
	 */
	fc_dependents = heap_truncate_find_FKs(fc_oids);
	if (fc_dependents == NIL)
		return;

	/*
	 * 否则，我们对每个关系重复扫描，以识别特定的关系对进行投诉。这非常慢，但在故障路径中性能并不重要。这样做的原因是确保生成的消息不依赖于 pg_constraint 中偶然的行位置。
	 */
	foreach(fc_cell, fc_oids)
	{
		Oid			fc_relid = lfirst_oid(fc_cell);
		ListCell   *fc_cell2;

		fc_dependents = heap_truncate_find_FKs(list_make1_oid(fc_relid));

		foreach(fc_cell2, fc_dependents)
		{
			Oid			fc_relid2 = lfirst_oid(fc_cell2);

			if (!list_member_oid(fc_oids, fc_relid2))
			{
				char	   *fc_relname = get_rel_name(fc_relid);
				char	   *fc_relname2 = get_rel_name(fc_relid2);

				if (fc_tempTables)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("unsupported ON COMMIT and foreign key combination"),
							 errdetail("Table \"%s\" references \"%s\", but they do not have the same ON COMMIT setting.",
									   fc_relname2, fc_relname)));
				else
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot truncate a table referenced in a foreign key constraint"),
							 errdetail("Table \"%s\" references \"%s\".",
									   fc_relname2, fc_relname),
							 errhint("Truncate table \"%s\" at the same time, "
									 "or use TRUNCATE ... CASCADE.",
									 fc_relname2)));
			}
		}
	}
}

/*
 * heap_truncate_find_FKs
 *		查找具有引用任何给定关系的外键的关系
 *
 * 输入和结果都是关系 OID 的列表。结果不包含重复项，不包括输入列表中已存在的任何关系，并根据 OID 排序。（最后一个属性主要是为了确保回归测试中的一致行为；我们不希望行为因 pg_constraint 中行的偶然位置而变化。）
 *
 * 注意：调用者应该已经对 relationIds 中提到的所有关系获得了适当的锁。由于添加或删除外键需要对两个关系进行独占锁，因此这确保答案是稳定的。
 */
List * heap_truncate_find_FKs(List *fc_relationIds)
{
	List	   *fc_result = NIL;
	List	   *fc_oids;
	List	   *fc_parent_cons;
	ListCell   *fc_cell;
	ScanKeyData fc_key;
	Relation	fc_fkeyRel;
	SysScanDesc fc_fkeyScan;
	HeapTuple	fc_tuple;
	bool		fc_restart;

	fc_oids = list_copy(fc_relationIds);

	/*
	 * 必须扫描 pg_constraint。现在，它是顺序扫描，因为在 confrelid 上没有可用的索引。
	 */
	fc_fkeyRel = table_open(ConstraintRelationId, AccessShareLock);

restart:
	fc_restart = false;
	fc_parent_cons = NIL;

	fc_fkeyScan = systable_beginscan(fc_fkeyRel, InvalidOid, false,
								  NULL, 0, NULL);

	while (HeapTupleIsValid(fc_tuple = systable_getnext(fc_fkeyScan)))
	{
		Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

		/* 不是外键 */
		if (fc_con->contype != CONSTRAINT_FOREIGN)
			continue;

		/* 不引用我们列表中的任何表 */
		if (!list_member_oid(fc_oids, fc_con->confrelid))
			continue;

		/*
		 * 如果此约束有一个我们尚未看到的父约束，请在下面的第二轮中跟踪它。跟踪父约束使我们能够向上到顶级约束并查找引用分区表的所有可能关系。
		 */
		if (OidIsValid(fc_con->conparentid) &&
			!list_member_oid(fc_parent_cons, fc_con->conparentid))
			fc_parent_cons = lappend_oid(fc_parent_cons, fc_con->conparentid);

		/*
		 * 将引用者添加到结果中，除非它已经在输入列表中。（不必担心重复：我们会在下面修复这个问题）。
		 */
		if (!list_member_oid(fc_relationIds, fc_con->conrelid))
			fc_result = lappend_oid(fc_result, fc_con->conrelid);
	}

	systable_endscan(fc_fkeyScan);

	/*
	 * 处理我们找到的每个父约束，将它们引用的关系列表添加到 oids 列表中。如果我们确实添加了任何新的此类关系，请重新执行上面的第一轮。此外，如果我们看到父约束本身有父约束，请添加它，以便我们在一次额外的遍历中处理所有关系。
	 */
	foreach(fc_cell, fc_parent_cons)
	{
		Oid			fc_parent = lfirst_oid(fc_cell);

		ScanKeyInit(&fc_key,
					Anum_pg_constraint_oid,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_parent));

		fc_fkeyScan = systable_beginscan(fc_fkeyRel, ConstraintOidIndexId,
									  true, NULL, 1, &fc_key);

		fc_tuple = systable_getnext(fc_fkeyScan);
		if (HeapTupleIsValid(fc_tuple))
		{
			Form_pg_constraint fc_con = (Form_pg_constraint) GETSTRUCT(fc_tuple);

			/*
			 * pg_constraint 行在分区层次结构中总是以这种方式出现：在约束的每一侧，每个指向另一侧顶层表的分区都有一行。
			 *
			 * 由于这种安排，我们可以通过将所有有效的 conparentid 行添加到 'parent_cons' 中，以及将所有具有零 conparentid 的行添加到 'oids' 列中，从而正确捕获所有相关关系。如果任何 oids 被添加到 'oids'，请通过设置 'restart' 重新执行上面的第一轮。
			 */
			if (OidIsValid(fc_con->conparentid))
				fc_parent_cons = list_append_unique_oid(fc_parent_cons,
													 fc_con->conparentid);
			else if (!list_member_oid(fc_oids, fc_con->confrelid))
			{
				fc_oids = lappend_oid(fc_oids, fc_con->confrelid);
				fc_restart = true;
			}
		}

		systable_endscan(fc_fkeyScan);
	}

	list_free(fc_parent_cons);
	if (fc_restart)
		goto restart;

	table_close(fc_fkeyRel, AccessShareLock);
	list_free(fc_oids);

	/* 现在对结果列表进行排序和去重 */
	list_sort(fc_result, list_oid_cmp);
	list_deduplicate_oid(fc_result);

	return fc_result;
}

/*
 * StorePartitionKey
 *		将分区键关系的信息存储到目录中
 */
void StorePartitionKey(Relation fc_rel,
				  char fc_strategy,
				  int16 fc_partnatts,
				  AttrNumber *fc_partattrs,
				  List *fc_partexprs,
				  Oid *fc_partopclass,
				  Oid *fc_partcollation)
{
	int			fc_i;
	int2vector *fc_partattrs_vec;
	oidvector  *fc_partopclass_vec;
	oidvector  *fc_partcollation_vec;
	Datum		fc_partexprDatum;
	Relation	pg_partitioned_table;
	HeapTuple	fc_tuple;
	Datum		fc_values[Natts_pg_partitioned_table];
	bool		fc_nulls[Natts_pg_partitioned_table];
	ObjectAddress fc_myself;
	ObjectAddress fc_referenced;
	ObjectAddresses *fc_addrs;

	Assert(fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);

	/* 将分区属性编号、opclass OIDs 复制到数组中 */
	fc_partattrs_vec = buildint2vector(fc_partattrs, fc_partnatts);
	fc_partopclass_vec = buildoidvector(fc_partopclass, fc_partnatts);
	fc_partcollation_vec = buildoidvector(fc_partcollation, fc_partnatts);

	/* 将表达式（如果有）转换为文本数据 */
	if (fc_partexprs)
	{
		char	   *fc_exprString;

		fc_exprString = nodeToString(fc_partexprs);
		fc_partexprDatum = CStringGetTextDatum(fc_exprString);
		pfree(fc_exprString);
	}
	else
		fc_partexprDatum = (Datum) 0;

	pg_partitioned_table = table_open(PartitionedRelationId, RowExclusiveLock);

	MemSet(fc_nulls, false, sizeof(fc_nulls));

	/* 只有这个可以为空 */
	if (!fc_partexprDatum)
		fc_nulls[Anum_pg_partitioned_table_partexprs - 1] = true;

	fc_values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(fc_rel));
	fc_values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(fc_strategy);
	fc_values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(fc_partnatts);
	fc_values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
	fc_values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(fc_partattrs_vec);
	fc_values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(fc_partopclass_vec);
	fc_values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(fc_partcollation_vec);
	fc_values[Anum_pg_partitioned_table_partexprs - 1] = fc_partexprDatum;

	fc_tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), fc_values, fc_nulls);

	CatalogTupleInsert(pg_partitioned_table, fc_tuple);
	table_close(pg_partitioned_table, RowExclusiveLock);

	/* 将此关系标记为依赖于以下一些内容 */
	fc_addrs = new_object_addresses();
	ObjectAddressSet(fc_myself, RelationRelationId, RelationGetRelid(fc_rel));

	/* 每个键列的操作符类和排序规则 */
	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		ObjectAddressSet(fc_referenced, OperatorClassRelationId, fc_partopclass[fc_i]);
		add_exact_object_address(&fc_referenced, fc_addrs);

		/* 默认排序规则是固定的，因此不必记录它 */
		if (OidIsValid(fc_partcollation[fc_i]) &&
			fc_partcollation[fc_i] != DEFAULT_COLLATION_OID)
		{
			ObjectAddressSet(fc_referenced, CollationRelationId, fc_partcollation[fc_i]);
			add_exact_object_address(&fc_referenced, fc_addrs);
		}
	}

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	/*
	 * 分区列在内部被标记为依赖于表，因为我们不能在不删除整个表的情况下删除任何一个。(ATExecDropColumn 独立地强制执行这一点，但并不是万无一失的，因此我们也需要这些依赖关系。)
	 */
	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		if (fc_partattrs[fc_i] == 0)
			continue;			/* 此处忽略表达式 */

		ObjectAddressSubSet(fc_referenced, RelationRelationId,
							RelationGetRelid(fc_rel), fc_partattrs[fc_i]);
		recordDependencyOn(&fc_referenced, &fc_myself, DEPENDENCY_INTERNAL);
	}

	/*
	 * 还要考虑在分区表达式中提到的任何内容。外部引用（例如，函数）获得正常依赖关系。表达式中提到的表列的处理与普通分区列相同，即它们在内部依赖于整个表。
	 */
	if (fc_partexprs)
		recordDependencyOnSingleRelExpr(&fc_myself,
										(Node *) fc_partexprs,
										RelationGetRelid(fc_rel),
										DEPENDENCY_NORMAL,
										DEPENDENCY_INTERNAL,
										true /* 反转自依赖 */ );

	/*
	 * 我们必须使 relcache 无效，以便下一个
	 * CommandCounterIncrement() 将导致使用刚创建的目录条目中的信息重建。
	 */
	CacheInvalidateRelcache(fc_rel);
}

/*
 *	RemovePartitionKeyByRelId
 *		删除关系的 pg_partitioned_table 条目
 */
void RemovePartitionKeyByRelId(Oid fc_relid)
{
	Relation	fc_rel;
	HeapTuple	fc_tuple;

	fc_rel = table_open(PartitionedRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for partition key of relation %u",
			 fc_relid);

	CatalogTupleDelete(fc_rel, &fc_tuple->t_self);

	ReleaseSysCache(fc_tuple);
	table_close(fc_rel, RowExclusiveLock);
}

/*
 * StorePartitionBound
 *		更新 rel 的 pg_class 元组以存储分区边界并将
 *		relispartition 设置为 true
 *
 * 如果这是默认分区，还需更新 pg_partitioned_table 中的默认分区 OID。
 *
 * 此外，使父关系的 relcache 无效，以便下次重建时将新分区的信息加载到其分区描述符中。如果存在默认分区，我们还必须使其 relcache 条目无效。
 */
void StorePartitionBound(Relation fc_rel, Relation fc_parent, PartitionBoundSpec *fc_bound)
{
	Relation	fc_classRel;
	HeapTuple	fc_tuple,
				fc_newtuple;
	Datum		fc_new_val[Natts_pg_class];
	bool		fc_new_null[Natts_pg_class],
				fc_new_repl[Natts_pg_class];
	Oid			fc_defaultPartOid;

	/* 更新 pg_class 元组 */
	fc_classRel = table_open(RelationRelationId, RowExclusiveLock);
	fc_tuple = SearchSysCacheCopy1(RELOID,
								ObjectIdGetDatum(RelationGetRelid(fc_rel)));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u",
			 RelationGetRelid(fc_rel));

#ifdef USE_ASSERT_CHECKING
	{
		Form_pg_class fc_classForm;
		bool		fc_isnull;

		fc_classForm = (Form_pg_class) GETSTRUCT(fc_tuple);
		Assert(!fc_classForm->relispartition);
		(void) SysCacheGetAttr(RELOID, fc_tuple, Anum_pg_class_relpartbound,
							   &fc_isnull);
		Assert(fc_isnull);
	}
#endif

	/* 填充 relpartbound 值 */
	memset(fc_new_val, 0, sizeof(fc_new_val));
	memset(fc_new_null, false, sizeof(fc_new_null));
	memset(fc_new_repl, false, sizeof(fc_new_repl));
	fc_new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(fc_bound));
	fc_new_null[Anum_pg_class_relpartbound - 1] = false;
	fc_new_repl[Anum_pg_class_relpartbound - 1] = true;
	fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_classRel),
								 fc_new_val, fc_new_null, fc_new_repl);
	/* 还设置标志 */
	((Form_pg_class) GETSTRUCT(fc_newtuple))->relispartition = true;

	/*
	 * 我们已经检查了没有继承子类，但重置
	 * relhassubclass 以防它被遗留。
	 */
	if (fc_rel->rd_rel->relkind == RELKIND_RELATION && fc_rel->rd_rel->relhassubclass)
		((Form_pg_class) GETSTRUCT(fc_newtuple))->relhassubclass = false;

	CatalogTupleUpdate(fc_classRel, &fc_newtuple->t_self, fc_newtuple);
	heap_freetuple(fc_newtuple);
	table_close(fc_classRel, RowExclusiveLock);

	
/*
	 * 如果我们正在为默认分区存储边界，更新
	 * pg_partitioned_table 也是必要的。
	 */
	if (fc_bound->is_default)
		update_default_partition_oid(RelationGetRelid(fc_parent),
									 RelationGetRelid(fc_rel));

	/* 使这些更新可见 */
	CommandCounterIncrement();

	/*
	 * 默认分区的分区约束依赖于其他每个分区的
	 * 分区边界，因此每次添加或删除分区时
	 * 我们必须使该分区的 relcache 条目失效。
	 */
	fc_defaultPartOid =
		get_default_oid_from_partdesc(RelationGetPartitionDesc(fc_parent, true));
	if (OidIsValid(fc_defaultPartOid))
		CacheInvalidateRelcacheByRelid(fc_defaultPartOid);

	CacheInvalidateRelcache(fc_parent);
}
